1. Project Overview & Quickstart (protocolbuffers/objectivec)
objectivec
Open-source repository protocolbuffers/objectivec
Repository Details
- Repository: protocolbuffers/objectivec
- Primary Language: Code
Note: High-volume repository documentation is actively indexed and synchronized by YakaAI.
2. Official Technical Reference & Guides (protocolbuffers/protocolbuffers.github.io)
File: README.md
Protocol Buffers - Google's data interchange format
Copyright 2008 Google Inc.
Overview
Protocol Buffers (a.k.a., protobuf) are Google's language-neutral,
platform-neutral, extensible mechanism for serializing structured data. This
site holds Protocol Buffers documentation.
Protocol Buffers Repository
The Protocol Buffers GitHub repository is
https://github.com/protocolbuffers/protobuf.
Developer Community
To be alerted to upcoming changes in Protocol Buffers and connect with protobuf
developers and users,
join the Google Group.
File: content/best-practices/_index.md
+++
title = "Proto Best Practices"
weight = 90
description = "An overview of best practices topics."
type = "docs"
no_list = "true"
+++
Best practices content for defining and using protos exists in the following
topics:
File: content/best-practices/1-1-1.md
+++
title = "1-1-1 Best Practice"
weight = 105
linkTitle = "1-1-1 Best Practice"
description = "All proto definitions should have one top-level element and build target per file."
type = "docs"
aliases = "/programming-guides/1-1-1"
+++
The "1-1-1" best practice advocates structuring definitions with one top-level
entity (message, enum, or extension) per .proto file, corresponding to a
single proto_library build rule. This approach promotes small, modular proto
definitions. Key benefits include simplified refactoring, potentially improved
build times, and smaller binary sizes due to minimized transitive dependencies.
Rationale
The 1-1-1 best practice is to keep every proto_library and .proto file as small
as is reasonable, with the ideal being:
- One
proto_librarybuild rule - One source
.protofile - One top-level entity (message, enum, or extension)
Having the fewest number of message, enum, extension, and services as you
reasonably can makes refactoring easier. Moving files when they're separated is
much easier than extracting messages from a file with other messages.
Following this practice can help build times and binary size by reducing the
size of your transitive dependencies in practice: when some code only needs to
use one enum, under a 1-1-1 design it can depend just on the .proto file that
defines that enum and avoid incidentally pulling in a large set of transitive
dependencies that may only be used by another message defined in the same file.
There are cases where the 1-1-1 ideal is not possible (circular dependencies),
not ideal (extremely conceptually coupled messages which have readability
benefits by being co-located), or where some of the downsides don't apply (when
a .proto file has no imports, then there are no technical concerns about the
size of transitive dependencies). As with any best practice, use good judgment
for when to diverge from the guideline.
One place that modularity of proto schema files is important is when creating
gRPC
definitions. The following set of proto files shows modular structure.
student_id.proto
edition = "2023";
package my.package;
message StudentId {
string value = 1;
}full_name.proto
edition = "2023";
package my.package;
message FullName {
string family_name = 1;
string given_name = 2;
}student.proto
edition = "2023";
package my.package;
import "student_id.proto";
import "full_name.proto";
message Student {
StudentId id = 1;
FullName name = 2;
}create_student_request.proto
edition = "2023";
package my.package;
import "full_name.proto";
message CreateStudentRequest {
FullName name = 1;
}create_student_response.proto
edition = "2023";
package my.package;
import "student.proto";
message CreateStudentResponse {
Student student = 1;
}get_student_request.proto
edition = "2023";
package my.package;
import "student_id.proto";
message GetStudentRequest {
StudentId id = 1;
}get_student_response.proto
edition = "2023";
package my.package;
import "student.proto";
message GetStudentResponse {
Student student = 1;
}student_service.proto
edition = "2023";
package my.package;
import "create_student_request.proto";
import "create_student_response.proto";
import "get_student_request.proto";
import "get_student_response.proto";
service StudentService {
rpc CreateStudent(CreateStudentRequest) returns (CreateStudentResponse);
rpc GetStudent(GetStudentRequest) returns (GetStudentResponse);
}The service definition and each of the message definitions are each in their own
file, and you use includes to give access to the messages from other schema
files.
In this example, Student, StudentId, and FullName are domain types that
are reusable across requests and responses. The top-level request and response
protos are unique to each service+method.
If you later need to add a middle_name field to the FullName message, you
won't need to update every individual top-level message with that new field.
Likewise, if you need to update Student with more information, all the
requests and responses get the update. Further, StudentId might update to be a
multi-part ID.
Lastly, having even simple types like StudentId wrapped as a message means
that you have created a type that has semantics and consolidated documentation.
For something like FullName you'll need to be careful with where this PII gets
logged; this is another advantage of not repeating these fields in multiple
top-level messages. You can tag those fields in one place as sensitive
and exclude them from logging.
File: content/best-practices/dos-donts.md
+++
title = "Proto Best Practices"
weight = 90
description = "Shares vetted best practices for authoring Protocol Buffers."
type = "docs"
aliases = "/programming-guides/dos-donts"
+++
Clients and servers are never updated at exactly the same time - even when you
try to update them at the same time. One or the
other may get rolled back. Donโt assume that you can make a breaking change and
it'll be okay because the client and server are in sync.
**Don't** Re-use a Tag Number {#reuse-number}
Never re-use a tag number. It messes up deserialization. Even if you think no
one is using the field, donโt re-use a tag number. If the change was live ever,
there could be serialized versions of your proto in a log
somewhere. Or there could be old code in another server that will break.
**Do** Reserve Tag Numbers for Deleted Fields {#reserve-tag-numbers}
When you delete a field that's no longer used, reserve its tag number so that no
one accidentally re-uses it in the future. Just reserved 2, 3; is enough. No
type required (lets you trim dependencies!). You can also reserve names to avoid
recycling now-deleted field names: reserved foo, bar;. See the docs on
reserved field numbers
and
reserved field names.
**Do** Reserve Numbers for Deleted Enum Values {#reserve-deleted-numbers}
When you delete an enum value that's no longer used, reserve its number so that
no one accidentally re-uses it in the future. Just reserved 2, 3; is enough.
You can also reserve names to avoid recycling now-deleted value names: reserved FOO, BAR;. See also
enum reserved values
**Do** Put New Enum Aliases Last {#enum-aliases-last}
When you add a new enum alias, put the new name last to give
services time to pick it up.
To safely remove the original name (if it's being used for interchange, which it
shouldn't), you must do the following:
Add the new name below the old name and deprecate the old one (serializers
will continue to use the old name)After every parser has the schema rolled out, swap the order of the two
names (serializers will begin using the new name, parsers accept both)After every serializer has that version of the schema, you can delete the
deprecated name.
Note: While in theory clients shouldn't be using the old name for
interchange, it's still polite to follow the above steps, especially for
widely-used enum names.
**Don't** Change the Type of a Field {#change-type}
Almost never change the type of a field; it'll mess up deserialization, same as
re-using a tag number. The
protobuf docs
outline cases where old binary encoded data may successfully parse under a
different type (for example, going between int32, uint32, int64 andbool). However, changing a field's type can be difficult to roll out safely
even when the new schema can successfully parse old data.
**Don't** Add a Required Field {#add-required}
Never add a required field, instead add // required to document the API
contract. Required fields are considered harmful by so many they were
removed from proto3 completely. Make all fields
optional or repeated. You never know how long a message type is going to last
and whether someone will be forced to fill in your required field with an empty
string or zero in four years when itโs no longer logically required but the
proto still says it is.
For proto3 there are no required fields, so this advice does not apply.
**Don't** Make a Message with Lots of Fields {#lots-of-fields}
Don't make a message with โlotsโ (think: hundreds) of fields. In C++ every field
adds roughly 65 bits to the in-memory object size whether itโs populated or not
(8 bytes for the pointer and, if the field is declared as optional, another bit
in a bitfield that keeps track of whether the field is set). When your proto
grows too large, the generated code may not even compile (for example, in Java
there is a hard limit on the size of a method
).
**Do** Include an Unspecified Value in an Enum {#unspecified-enum}
Enums should include a default FOO_UNSPECIFIED value as the first value in the
declaration.
When new values are added to an enum, old clients will see the field as unset
and the getter will return the default value or the first-declared value if no
default exists . For consistent behavior with proto enums,
the first declared enum value should be a default FOO_UNSPECIFIED value and
should use tag 0. It may be tempting to declare this default as a semantically
meaningful value but as a general rule, do not, to aid in the evolution of your
protocol as new enum values are added over time. All enum values declared under
a container message are in the same C++ namespace, so prefix the unspecified
value with the enumโs name to avoid compilation errors. If you'll never need
cross-language constants, an int32 will preserve unknown values and generates
less code. Note that proto enums require the first value to be
zero and can round-trip (deserialize, serialize) an unknown enum value.
**Don't** Use C/C++ Macro Constants for Enum Values {#macro-constants}
Using words that have already been defined by the C++ language - specifically,
in its headers such as math.h, may cause compilation errors if the #include
statement for one of those headers appears before the one for .proto.h. Avoid
using macro constants such as "NULL," "NAN," and "DOMAIN" as enum values.
**Do** prefer extensions over `Any` where possible {#avoid-any}
If you plan to use Any, first double-check if extensions can satisfy the use
case instead. If yes, prefer using extensions.
Any was created in Proto3 to replace extensions. However, it has a number of
design flaws. For most use cases, prefer using extensions.
The main use case that Any fulfills is the uncommon scenario in which
horizontal infrastructure needs to propagate completely arbitrary messages, and
declaring an extension for each possible legal types would be infeasible.
In the future, Protobuf may introduce a new concept that would satisfy the Any
use cases without the design flaws, but we have no concrete plans at this time.
**Do** Use Well-Known Types and Common Types {#well-known-common}
Using the following common, shared types is strongly encouraged. E.g., do not
use int32 timestamp_seconds_since_epoch or int64 timeout_millis in your code
when a perfectly suitable common type already exists!
duration
is a signed, fixed-length span of time (for example, 42s).timestamp
is a point in time independent of any time zone or calendar (for example,
2017-01-15T01:30:15.01Z).interval
is a time interval independent of time zone or calendar (for example,
2017-01-15T01:30:15.01Z - 2017-01-16T02:30:15.01Z).date
is a whole calendar date (for example, 2005-09-19).month
is a month of year (for example, April).dayofweek
is a day of week (for example, Monday).timeofday
is a time of day (for example, 10:42:23).field_mask
is a set of symbolic field paths (for example, f.b.d).postal_address
is a postal address (for example, 1600 Amphitheatre Parkway Mountain View,
CA 94043 USA).money
is an amount of money with its currency type (for example, 42 USD).latlng
is a latitude/longitude pair (for example, 37.386051 latitude and
-122.083855 longitude).color
is a color in the RGBA color space.
Note: While the "Well-Known Types" (such as Duration and Timestamp) are
included with the Protocol Buffers compiler, the "Common Types" (such as Date
and Money) are not. To use the Common Types, you may need to add a dependency
on the googleapis repository.
**Do** Define Message Types in Separate Files {#separate-files}
When defining a proto schema, you should have a single message, enum, extension,
service, or group of cyclic dependencies per file. This makes refactoring
easier. Moving files when they're separated is much easier than extracting
messages from a file with other messages. Following this practice also helps to
keep the proto schema files smaller, which enhances maintainability.
If they will be widely used outside of your project, consider putting them in
their own file with no dependencies. Then it's easy for anyone to use those
types without introducing the transitive dependencies in your other proto files.
For more on this topic, see
1-1-1 Rule.
**Don't** Change the Default Value of a Field {#change-default-value}
Almost never change the default value of a proto field. This causes version skew
between clients and servers. A client reading an unset value will see a
different result than a server reading the same unset value when their builds
straddle the proto change. Proto3 removed the ability to
set default values.
**Don't** Go from Repeated to Scalar {#repeated-to-scalar}
Although it won't cause crashes, you'll lose data. For JSON, a mismatch in
repeatedness will lose the whole message. For numeric proto3 fields and proto2packed fields, going from repeated to scalar will lose all data in that
field. For non-numeric proto3 fields and un-annotated proto2 fields, going
from repeated to scalar will result in the last deserialized value "winning."
Going from scalar to repeated is OK in proto2 and in proto3 with[packed=false] because for binary serialization the scalar value becomes a
one-element list.
**Do** Follow the Style Guide for Generated Code {#follow-style-guide}
Proto generated code is referred to in normal code. Ensure that options in.proto file do not result in generation of code which violate the style guide.
For example:
java_outer_classnameshould follow
https://google.github.io/styleguide/javaguide.html#s5.2.2-class-namesjava_packageandjava_alt_packageshould follow
https://google.github.io/styleguide/javaguide.html#s5.2.1-package-namespackage, although used for Java whenjava_packageis not present, always
directly corresponds to C++ namespace and thus should follow
https://google.github.io/styleguide/cppguide.html#Namespace_Names.
If these style guides conflict, usejava_packagefor Java.ruby_packageshould be in the formFoo::Bar::Bazrather thanFoo.Bar.Baz.
**Don't** use Text Format Messages for Interchange {#text-format-interchange}
Text-based serialization formats like text format and
JSON represent fields and enum values as strings. As a result, deserialization
of protocol buffers in these formats using old code will fail when a field or
enum value is renamed, or when a new field or enum value or extension is added.
Use binary serialization when possible for data interchange, and use text format
for human editing and debugging only.
If you use protos converted to JSON in your API or for storing data, you may not
be able to safely rename fields or enums at all.
**Never** Rely on Serialization Stability Across Builds {#serialization-stability}
The stability of proto serialization is not guaranteed across binaries or across
builds of the same binary. Do not rely on it when, for example, building cache
keys.
**Don't** Generate Java Protos in the Same Java Package as Other Code {#generate-java-protos}
Generate Java proto sources into a separate package from your hand-written Java
sources. The package, java_package and java_alt_api_package options
control
where the generated Java sources are emitted.
Make sure hand-written Java source code does not also live in that same package.
A common practice is to generate your protos into a proto subpackage in your
project that only contains those protos (that is, no hand-written source
code).
**Do** Derive Java Package from the .proto Package (if overridden) {#derive-java-package}
Setting the java_package can introduce fully-qualified name collisions in
generated code that did not exist in the .proto semantics. For example, these
two files may create collisions in the generated code despite the
fully-qualified names not colliding in the original schema:
package x;
option java_package = "com.example.proto";
message Abc {}package y;
option java_package = "com.example.proto";
message Abc {}To avoid these problems, you should never set the same java_package in two
files that have different .proto packages set.
The best practice is to establish a local naming pattern where the package name
is derived from the .proto package. For example, a best practice file withpackage y might consistently set option java_package = "com.example.proto.y".
This guidance also applies to any other language-specific options where package
overrides are possible.
Avoid Using Language Keywords for Field Names {#avoid-keywords}
If the name of a message, field, enum, or enum value is a keyword in the
language that reads from/writes to that field, then protobuf may change the
field name, and may have different ways to access them than normal fields. For
example, see
this warning about Python.
You should also avoid using keywords in your file paths, as this can also cause
problems.
**Do** Use Different Messages For RPC APIs and Storage {#separate-types-for-storage}
Reusing the same messages for APIs and long-term storage may seem convenient,
reducing boilerplate and overhead of conversion between messages.
However, the needs of long-term storage and live RPC services tend to later
diverge. Using separate types even if they are largely duplicative initially
gives freedom to change your storage format without impacting your external
clients. Layer your code so that modules deal either with client protos, storage
protos, or translation.
There is a cost in maintaining the translation layer, but it quickly pays off
once you have clients and have to do your first storage changes.
**Don't** Use Booleans for Something That Has Two States Now, but Might Have More Later {#bad-bools}
If you are using boolean for a field, make sure that the field is indeed
describing just two possible states (for all time, not just now and the near
future). The future flexibility of using an enum is often worth it, even if it
only has two values when it is first introduced.
message Photo {
// Bad: True if it's a GIF.
optional bool gif;
// Good: File format of the referenced photo (for example, GIF, WebP, PNG).
optional PhotoType type;
}**Do** Use java_outer_classname (before Edition 2024) {#java-outer-classname}
Every proto schema definition file should set option java_outer_classname to
the .proto file name converted to TitleCase with the '.' removed. For example,
the file student_record_request.proto should set:
option java_outer_classname = "StudentRecordRequestProto";The default behavior of Edition 2024 files has been aligned with this
recommendation, so no option should be set when using Edition 2024 or later.
File: content/best-practices/no-cargo-cults.md
+++
title = "Avoid Cargo Culting"
weight = 90
description = "Avoid using features where they are not needed."
type = "docs"
+++
Do not
cargo cult
settings in proto files. If you are creating a new proto file based on existing
schema definitions, don't apply option settings except for those that you
understand the need for.
Best Practices Specific to Editions {#editions}
Avoid applying editions features
except when they're actually necessary. Features in .proto files signal the
use of either experimental future behaviors or deprecated past behaviors. Best
practices for the latest edition will always be the default. New proto schema
definition content should remain feature-free, except if you want to early-adopt
a feature for future behavior that's being rolled out.
Copying-forward feature settings without understanding why they are set can lead
to unexpected behaviors in your code.
File: content/design-decisions/_index.md
+++
title = "Protobuf Team Design Decisions"
weight = 88
description = "Covers the design decisions the Protobuf team has made"
type = "docs"
no_list = "true"
linkTitle = "Design Decisions"
+++
This section contains some positions of the Protobuf team that inform our design
and implementation decisions.
These positions were taken after careful consideration and won't be overturned
on a whim, but are open to being revisited as we gain new information, and as
things develop in the broader ecosystem context.
File: content/design-decisions/nullable-getters-setters.md
+++
title = "No Nullable Setters/Getters Support"
weight = 89
description = "Covers why Protobuf doesn't support nullable setters and getters"
type = "docs"
aliases = "/programming-guides/nullable-getters-setters/"
+++
We have heard feedback that some folks would like protobuf to support nullable
getters/setters in their null-friendly language of choice (particularly Kotlin,
C#, and Rust). While this does seem to be a helpful feature for folks using
those languages, the design choice has tradeoffs which have led to the Protobuf
team choosing not to implement them.
Explicit presence is not a concept that directly maps to the traditional notion
of nullability. It is subtle, but explicit presence philosophy is closer to "the
fields are not nullable, but you can detect if the field was explicitly assigned
a value or not. Normal access will see some default value if it is not assigned,
but you can check if the field was actively written to or not, when needed."
The biggest reason not to have nullable fields is the intended behavior of
default values specified in a .proto file. By design, calling a getter on an
unset field will return the default value of that field.
Note: C# does treat message fields as nullable. This inconsistency with
other languages stems from the lack of immutable messages, which makes it
impossible to create shared immutable default instances. Because message fields
can't have defaults, there's no functional problem with this.
As an example, consider this .proto file:
message Msg { Child child = 1; }
message Child { Grandchild grandchild = 1; }
message Grandchild { int32 foo = 1 [default = 72]; }and corresponding Kotlin getters:
// With our API where getters are always non-nullable:
msg.child.grandchild.foo == 72
// With nullable submessages the ?. operator fails to get the default value:
msg?.child?.grandchild?.foo == null
// Or verbosely duplicating the default value at the usage site:
(msg?.child?.grandchild?.foo ?: 72)and corresponding Rust getters:
// With our API:
msg.child().grandchild().foo() // == 72
// Where every getter is an Option<T>, verbose and no default observed
msg.child().map(|c| c.grandchild()).map(|gc| gc.foo()) // == Option::None
// For the rare situations where code may want to observe both the presence and
// value of a field, the _opt() accessor which returns a custom Optional type
// can also be used here (the Optional type is similar to Option except can also
// be aware of the default value):
msg.child().grandchild().foo_opt() // Optional::Unset(72)If a nullable getter existed, it would necessarily ignore the user-specified
defaults (to return null instead) which would lead to surprising and
inconsistent behavior. If users of nullable getters want to access the default
value of the field, they would have to write their own custom handling to use
the default if null is returned, which removes the supposed benefit of
cleaner/easier code with null getters.
Similarly, we do not provide nullable setters as the behavior would be
unintuitive. Performing a set and then get would not always give the same value
back, and calling a set would only sometimes affect the has-bit for the field.
Note that message-typed fields are always explicit presence fields (with
hazzers). Proto3 defaults to scalar fields having implicit presence (without
hazzers) unless they are explicitly marked optional, while Proto2 does not
support implicit presence. With
Editions, explicit
presence is the default behavior unless an implicit presence feature is used.
With the forward expectation that almost all fields will have explicit presence,
the ergonomic concerns that come with nullable getters are expected to be more
of a concern than they may have been for Proto3 users.
Due to these issues, nullable setters/getters would radically change the way
default values can be used. While we understand the possible utility, we have
decided itโs not worth the inconsistencies and difficulty it introduces.
File: content/editions/_index.md
+++
title = "Protobuf Editions"
weight = 41
description = "Topics related to the Protobuf Editions functionality."
type = "docs"
+++
File: content/editions/features.md
+++
title = "Feature Settings for Editions"
weight = 43
description = "Protobuf Editions features and how they affect protobuf behavior."
type = "docs"
+++
This topic provides an overview of the features that are included in the
released edition versions. Subsequent editions' features will be added to this
topic. We announce new editions in
the News section.
Before configuring feature settings in your new schema definition content, make
sure you understand why you are using them. Avoid
cargo-culting with
features.
Prototiller {#prototiller}
Prototiller is a command-line tool that updates proto schema configuration files
between syntax versions and editions. It hasn't been released yet, but is
referenced throughout this topic.
Features {#features}
The following sections include all of the behaviors that are configurable using
features in editions. Preserving proto2 or proto3 Behavior shows
how to override the default behaviors so that your proto definition files act
like proto2 or proto3 files. For more information on how editions and features
work together to set behavior, see
Protobuf Editions Overview.
Feature settings apply at different levels:
File-level: These settings apply to all elements (messages, fields, enums,
and so on) that don't have an overriding setting.
Non-nested: Messages, enums, and services can override settings made at the
file level. They apply to everything within them (message fields, enum values)
that aren't overridden, but don't apply to other parallel messages and enums.
Nested: Oneofs, messages, and enums can override settings from the message
they're nested in.
Lowest-level: Fields, extensions, enum values, extension ranges, and methods
are the lowest level at which you can override settings.
Each of the following sections has a comment that states what scope the feature
can be applied to. The following sample shows a mock feature applied to each
scope:
edition = "2024";
// File-level scope definition
option features.bar = BAZ;
enum Foo {
// Enum (non-nested scope) definition
option features.bar = QUX;
A = 1;
B = 2;
}
message Corge {
// Message (non-nested scope) definition
option features.bar = QUUX;
message Garply {
// Message (nested scope) definition
option features.bar = WALDO;
string id = 1;
}
// Field (lowest-level scope) definition
Foo A = 1 [features.bar = GRAULT];
}In this example, the setting "GRAULT" in the lowest-level scope feature
definition overrides the non-nested-scope "QUUX" setting. And within the
Garply message, "WALDO" overrides "QUUX."
`features.default_symbol_visibility` {#symbol-vis}
This feature enables setting the default visibility for messages and enums,
making them available or unavailable when imported by other protos. Use of this
feature will reduce dead symbols in order to create smaller binaries.
In addition to setting the defaults for the entire file, you can use the local
and export keywords to set per-field behavior. Read more about this atexport / local Keywords.
Values available:
EXPORT_ALL: This is the default prior to Edition 2024. All messages and
enums are exported by default.EXPORT_TOP_LEVEL: All top-level symbols default to export; nested default
to local.LOCAL_ALL: All symbols default to local.STRICT: All symbols local by default. Nested types cannot be exported,
except for a special-case caveat formessage { enum {} reserved 1 to max; }.
Applicable to the following scope: file
Added in: Edition 2024
Default behavior per syntax/edition:
| Syntax/edition | Default |
|---|---|
| 2026 | STRICT |
| 2024 | EXPORT_TOP_LEVEL |
| 2023 | EXPORT_ALL |
| proto3 | EXPORT_ALL |
| proto2 | EXPORT_ALL |
Note: Feature settings on different schema elements
have different scopes.
The following sample shows how you can apply the feature to elements in your
proto schema definition files:
// foo.proto
edition = "2024";
// Symbol visibility defaults to EXPORT_TOP_LEVEL. Setting
// default_symbol_visibility overrides these defaults
option features.default_symbol_visibility = LOCAL_ALL;
// Top-level symbols are exported by default in Edition 2024; applying the local
// keyword overrides this
export message LocalMessage {
int32 baz = 1;
// Nested symbols are local by default in Edition 2024; applying the export
// keyword overrides this
enum ExportedNestedEnum {
UNKNOWN_EXPORTED_NESTED_ENUM_VALUE = 0;
}
}
// bar.proto
edition = "2024";
import "foo.proto";
message ImportedMessage {
// The following is valid because the imported message explicitly overrides
// the visibility setting in foo.proto
LocalMessage bar = 1;
// The following is not valid because default_symbol_visibility is set to
// `LOCAL_ALL`
// LocalMessage.ExportedNestedEnum qux = 2;
}`features.enforce_naming_style` {#enforce_naming}
Introduced in Edition 2024, this feature enables strict naming style enforcement
as defined in
the style guide to ensure
protos are round-trippable by default with a feature value to opt-out to use
legacy naming style.
Values available:
STYLE2026: Enforces strict adherence to the 2026 style guide for naming to
discourage common field-naming collisions.STYLE2024: Enforces strict adherence to the style guide for naming.STYLE_LEGACY: Applies the pre-Edition 2024 level of style guide
enforcement.
Applicable to the following scopes: file, extension range, message, field,
oneof, enum, enum value, service, method
Added in: Edition 2024
Default behavior per syntax/edition:
| Syntax/edition | Default |
|---|---|
| 2026 | STYLE2026 |
| 2024 | STYLE2024 |
| 2023 | STYLE_LEGACY |
| proto3 | STYLE_LEGACY |
| proto2 | STYLE_LEGACY |
Note: Feature settings on different schema elements
have different scopes.
The following code sample shows an Edition 2023 file:
Edition 2023 defaults to STYLE_LEGACY, so a non-conformant field name is fine:
edition = "2023";
message Foo {
// A non-conforming field name is not a problem
int64 bar_1 = 1;
}Edition 2024 defaults to STYLE2024, so an override is needed to keep the
non-conformant field name:
edition = "2024";
// To keep the non-conformant field name, override the STYLE2024 setting
option features.enforce_naming_style = STYLE_LEGACY;
message Foo {
int64 bar_1 = 1;
}The following code sample shows an Edition 2024 file:
Edition 2024 defaults to STYLE2024, so collision avoidance is not enforced:
edition = "2024";
// Field name collision avoidance is not enforced
message Bar {
int64 x = 1;
int64 has_x = 2;
}Edition 2026 defaults to STYLE2026, so an override is needed to keep the
colliding field names:
edition = "2024";
option features.enforce_naming_style = STYLE2024;
// To keep the colliding field names, override the STYLE2026 setting
message Bar {
int64 x = 1;
int64 has_x = 2;
}`features.enforce_proto_limits` {#enforce_proto_limits}
Introduced in Edition 2026, this feature enables structure size limit
enforcement in the Protobuf compiler when parsing .proto files to prevent
oversize message and enum definitions.
Values available:
PROTO_LIMITS2026: Enforces maximum limit checks:- Up to 1500 fields per message
- Up to 1000 oneofs per message
- Up to 1200 fields per oneof
- Up to 1700 values per enum
LEGACY_NO_EXPLICIT_LIMITS: Opts out of limit checking.
Applicable to the following scope: message, oneof, enum
Added in: Edition 2026
Default behavior per syntax/edition:
| Syntax/edition | Default |
|---|---|
| 2026 | PROTO_LIMITS2026 |
| 2024 | LEGACY_NO_EXPLICIT_LIMITS |
| 2023 | LEGACY_NO_EXPLICIT_LIMITS |
| proto3 | LEGACY_NO_EXPLICIT_LIMITS |
| proto2 | LEGACY_NO_EXPLICIT_LIMITS |
Note: Feature settings on different schema elements
have different scopes.
The following code sample shows an Edition 2024 file:
Edition 2024 defaults to LEGACY_NO_EXPLICIT_LIMITS, so a message with 1501
fields is fine:
edition = "2024";
message Foo {
// A message with more than 1500 fields
int64 one = 1;
int64 two = 2;
int64 three = 3;
# ...
int64 one_thousand_five_hundred = 1500;
int64 one_thousand_five_hundred_and_one = 1501;
}Edition 2026 defaults to PROTO_LIMITS2026, so an override is needed to keep
oversized messages:
edition = "2026";
// To keep the colliding field names, override the STYLE2026 setting
message Foo {
// Must be set at the level of the oversized message, enum, or oneof as this
// feature is not allowed at the file level.
option features.enforce_proto_limits = PROTO_LIMITS2026;
int64 one = 1;
int64 two = 2;
int64 three = 3;
# ...
int64 one_thousand_five_hundred = 1500;
int64 one_thousand_five_hundred_and_one = 1501;
}`features.enum_type` {#enum_type}
This feature sets the behavior for how enum values that aren't contained within
the defined set are handled. See
Enum Behavior for more
information on open and closed enums.
This feature doesn't impact proto3 files, so this section doesn't have a before
and after of a proto3 file.
Values available:
CLOSED:Closed enums store enum values that are out of range in the
unknown field set.OPEN:Open enums parse out of range values into their fields directly.
Applicable to the following scopes: file, enum
Added in: Edition 2023
Default behavior per syntax/edition:
| Syntax/edition | Default |
|---|---|
| 2026 | OPEN |
| 2024 | OPEN |
| 2023 | OPEN |
| proto3 | OPEN |
| proto2 | CLOSED |
Note: Feature settings on different schema elements
have different scopes.
The following code sample shows a proto2 file:
syntax = "proto2";
enum Foo {
A = 2;
B = 4;
C = 6;
}After running Prototiller, the equivalent code might look like
this:
edition = "2024";
enum Foo {
// Setting the enum_type feature overrides the default OPEN enum
option features.enum_type = CLOSED;
A = 2;
B = 4;
C = 6;
}`features.field_presence` {#field_presence}
This feature sets the behavior for tracking field presence, or the notion of
whether a protobuf field has a value.
Values available:
LEGACY_REQUIRED: The field is required for parsing and serialization.
Any explicitly-set value is
serialized onto the wire (even if it is the same as the default value).EXPLICIT: The field has explicit presence tracking. Any explicitly-set
value is serialized onto the wire (even if it is the same as the default
value). For singular primitive fields,has_*functions are generated for
fields set toEXPLICIT.IMPLICIT: The field has no presence tracking. The default value is not
serialized onto the wire (even if it is explicitly set).has_*functions
are not generated for fields set toIMPLICIT.
Applicable to the following scopes: file, field
Added in: Edition 2023
Default behavior per syntax/edition:
| Syntax/edition | Default |
|---|---|
| 2026 | EXPLICIT |
| 2024 | EXPLICIT |
| 2023 | EXPLICIT |
| proto3 | IMPLICIT* |
| proto2 | EXPLICIT |
* proto3 is IMPLICIT unless the field has the optional label, in which case
it behaves like EXPLICIT. See
Presence in Proto3 APIs
for more information.
Note: Feature settings on different schema elements
have different scopes.
The following code sample shows a proto2 file:
syntax = "proto2";
message Foo {
required int32 x = 1;
optional int32 y = 2;
repeated int32 z = 3;
}After running Prototiller, the equivalent code might look like this:
edition = "2024";
message Foo {
// Setting the field_presence feature retains the proto2 required behavior
int32 x = 1 [features.field_presence = LEGACY_REQUIRED];
int32 y = 2;
repeated int32 z = 3;
}The following shows a proto3 file:
syntax = "proto3";
message Bar {
int32 x = 1;
optional int32 y = 2;
repeated int32 z = 3;
}After running Prototiller, the equivalent code might look like this:
edition = "2024";
// Setting the file-level field_presence feature matches the proto3 implicit default
option features.field_presence = IMPLICIT;
message Bar {
int32 x = 1;
// Setting the field_presence here retains the explicit state that the proto3
// field has because of the optional syntax
int32 y = 2 [features.field_presence = EXPLICIT];
repeated int32 z = 3;
}Note that the required and optional labels no longer exist in Editions, as
the corresponding behavior is set explicitly with the field_presence feature.
`features.json_format` {#json_format}
This feature sets the behavior for JSON parsing and serialization.
This feature doesn't impact proto3 files, so this section doesn't have a before
and after of a proto3 file. Editions behavior matches the behavior in proto3.
Values available:
ALLOW: A runtime must allow JSON parsing and serialization. Checks are
applied at the proto level to make sure that there is a well-defined mapping
to JSON.LEGACY_BEST_EFFORT: A runtime does the best it can to parse and serialize
JSON. Certain protos are allowed that can result in unspecified behavior at
runtime (such as many:1 or 1:many mappings).
Applicable to the following scopes: file, message, enum
Added in: Edition 2023
Default behavior per syntax/edition:
| Syntax/edition | Default |
|---|---|
| 2026 | ALLOW |
| 2024 | ALLOW |
| 2023 | ALLOW |
| proto3 | ALLOW |
| proto2 | LEGACY_BEST_EFFORT |
Note: Feature settings on different schema elements
have different scopes.
The following code sample shows a proto2 file:
syntax = "proto2";
message Foo {
// Warning only
string bar = 1;
string bar_ = 2;
}After running Prototiller, the equivalent code might look like this:
edition = "2024";
option features.json_format = LEGACY_BEST_EFFORT;
message Foo {
string bar = 1;
string bar_ = 2;
}`features.message_encoding` {#message_encoding}
This feature sets the behavior for encoding fields when serializing.
This feature doesn't impact proto3 files, so this section doesn't have a before
and after of a proto3 file.
Depending on the language, fields that are "group-like" may have some unexpected
capitalization in generated code and in text-format, in order to provide
backwards compatibility with proto2. Message fields are "group-like" if all of
the following conditions are met:
- Has
DELIMITEDmessage encoding specified - Message type is defined in the same scope as the field
- Field name is exactly the lowercased type name
Values available:
LENGTH_PREFIXED: Fields are encoded using the LEN wire type described in
Message Structure.DELIMITED: Message-typed fields are encoded as
groups.
Applicable to the following scopes: file, field
Added in: Edition 2023
Default behavior per syntax/edition:
| Syntax/edition | Default |
|---|---|
| 2026 | LENGTH_PREFIXED |
| 2024 | LENGTH_PREFIXED |
| 2023 | LENGTH_PREFIXED |
| proto3 | LENGTH_PREFIXED |
| proto2 | LENGTH_PREFIXED |
Note: Feature settings on different schema elements
have different scopes.
The following code sample shows a proto2 file:
syntax = "proto2";
message Foo {
group Bar = 1 {
optional int32 x = 1;
repeated int32 y = 2;
}
}After running Prototiller, the equivalent code might look like this:
edition = "2024";
message Foo {
message Bar {
int32 x = 1;
repeated int32 y = 2;
}
Bar bar = 1 [features.message_encoding = DELIMITED];
}`features.repeated_field_encoding` {#repeated_field_encoding}
This feature is what the proto2/proto3packed option
for repeated fields has been migrated to in Editions.
Values available:
PACKED:Repeatedfields of a primitive type are encoded as a single LEN
record that contains each element concatenated.EXPANDED:Repeatedfields are each encoded with the field number for
each value.
Applicable to the following scopes: file, field
Added in: Edition 2023
Default behavior per syntax/edition:
| Syntax/edition | Default |
|---|---|
| 2026 | PACKED |
| 2024 | PACKED |
| 2023 | PACKED |
| proto3 | PACKED |
| proto2 | EXPANDED |
Note: Feature settings on different schema elements
have different scopes.
The following code sample shows a proto2 file:
syntax = "proto2";
message Foo {
repeated int32 bar = 6 [packed=true];
repeated int32 baz = 7;
}After running Prototiller, the equivalent code might look like this:
edition = "2024";
option features.repeated_field_encoding = EXPANDED;
message Foo {
repeated int32 bar = 6 [features.repeated_field_encoding=PACKED];
repeated int32 baz = 7;
}The following shows a proto3 file:
syntax = "proto3";
message Foo {
repeated int32 bar = 6;
repeated int32 baz = 7 [packed=false];
}After running Prototiller, the equivalent code might look like this:
edition = "2024";
message Foo {
repeated int32 bar = 6;
repeated int32 baz = 7 [features.repeated_field_encoding=EXPANDED];
}`features.utf8_validation` {#utf8_validation}
This feature sets how strings are validated. It applies to all languages except
where there's a language-specific utf8_validation feature that overrides it.
See features.(pb.java).utf8_validation for the
Java-language-specific feature.
Important: In Java, map<string, ...> fields always undergo UTF-8
validation, even when utf8_validation = NONE is configured. Because Java keys
will be represented as java.lang.String objects, disabling validation would
force the runtime to convert invalid byte sequences lossily using replacement
characters. This lossy conversion can result in unexpected behavior, such as key
collisions. As a wire-compatible workaround, consider using a repeated field
with a custom "map entry" message instead.
This feature doesn't impact proto3 files, so this section doesn't have a before
and after of a proto3 file.
Values available:
VERIFY: The runtime should verify UTF-8. This is the default proto3
behavior.NONE: The field behaves like an unverifiedbytesfield on the wire.
Parsers may handle this type of field in an unpredictable way, such as
replacing invalid characters. This is the default proto2 behavior.
Applicable to the following scopes: file, field
Added in: Edition 2023
Default behavior per syntax/edition:
| Syntax/edition | Default |
|---|---|
| 2026 | VERIFY |
| 2024 | VERIFY |
| 2023 | VERIFY |
| proto3 | VERIFY |
| proto2 | NONE |
Note: Feature settings on different schema elements
have different scopes.
The following code sample shows a proto2 file:
syntax = "proto2";
message MyMessage {
string foo = 1;
}After running Prototiller, the equivalent code might look like this:
edition = "2024";
message MyMessage {
string foo = 1 [features.utf8_validation = NONE];
}Language-specific Features {#lang-specific}
Some features apply to specific languages, and not to the same protos in other
languages. Using these features requires you to import the corresponding
*_features.proto file from the language's runtime. The examples in the following
sections show these imports.
`features.(pb.go).api_level` {#go-api_level}
Languages: Go
The api_level feature enables you to select which API version the Go protobuf
plugin should generate code for. The Opaque API is the latest version of the
Protocol Buffers implementation for the Go programming language. The previous
version is now called Open Struct API. See the
Go Protobuf: Releasing the Opaque API
blog post for an introduction.
Values available:
API_OPEN: The Open Struct API generates struct types that are open to
direct access.API_HYBRID: Hybrid is a step between Open and Opaque: The Hybrid API also
includes accessor methods (so you can update your code), but still exports
the struct fields as before. There is no performance difference; this API
level only helps with the migration.API_OPAQUE: With the Opaque API, the struct fields are hidden and can no
longer be directly accessed. Instead, the new accessor methods allow for
getting, setting, or clearing a field.
Applicable to the following scopes: message, file
Added in: Edition 2023
Default behavior per syntax/edition:
| Syntax/edition | Default |
|---|---|
| 2026 | API_OPAQUE |
| 2024 | API_OPAQUE |
| 2023 | API_OPEN |
Note: Feature settings on different schema elements
have different scopes.
You can set the api_level feature starting in edition 2023:
edition = "2023";
import option "third_party/golang/protobuf/v2/src/google/protobuf/go_features.proto";
// Remove this line after migrating the code to the Opaque API.
option features.(pb.go).api_level = API_HYBRID;NOTE: import option feature was added to reduce any impact that these added
imports may have on runtime reflection behaviors or other descriptor processing.
Always use import option if you are importing another .proto file only to
use it in options.
See also:
Opaque API: Migration
`features.(pb.cpp).enum_name_uses_string_view` {#enum-name-string-view}
Languages: C++
Before Edition 2024, all generated enum types provide the following function to
obtain the label out of an enum value, which has some overhead to construct thestd::string instances at runtime:
const std::string& Foo_Name(int);The default feature value in Edition 2024 changes this signature to returnabsl::string_view to allow for better storage decoupling and potential
memory/CPU savings. If you aren't ready to migrate, yet, you can override this
to set it back to its previous behavior. See
string_view return type
in the migration guide for more on this topic.
Values available:
true: The enum usesstring_viewfor its values.false: The enum usesstd::stringfor its values.
Applicable to the following scopes: file, enum
Added in: Edition 2024
Default behavior per syntax/edition:
| Syntax/edition | Default |
|---|---|
| 2026 | true |
| 2024 | true |
| 2023 | false |
| proto3 | false |
| proto2 | false |
Note: Feature settings on different schema elements
have different scopes.
`features.(pb.cpp).repeated_type` {#cpp-repeated_type}
Languages: C++
When set to PROXY, repeated field accessors return proxy types
(RepeatedFieldProxy) instead of pointers or references to RepeatedField orRepeatedPtrField. Proxies allow for a more efficient in-memory representation
of repeated fields in messages.
Importing this feature requires import option "google/protobuf/cpp_features.proto";
Values available:
PROXY: Accessors return proxy types.LEGACY: Accessors return legacyRepeatedFieldorRepeatedPtrField
pointers/references.
Applicable to the following scopes: file, field
Added in: Edition 2026
Default behavior per syntax/edition:
| Syntax/edition | Default |
|---|---|
| 2026 | LEGACY |
| 2024 | LEGACY |
| 2023 | LEGACY |
| proto3 | LEGACY |
| proto2 | LEGACY |
Note: Feature settings on different schema elements
have different scopes.
`features.(pb.java).large_enum` {#java-large_enum}
Languages: Java
This language-specific feature enables you to adopt new functionality that
handles large enums in Java without causing compiler errors. Note that this
feature replicates enum-like behavior but has some notable differences. For
example, switch statements are not supported.
Values available:
true: Java enums will use the new functionality.false: Java enums will continue to use Java enums.
Applicable to the following scopes: file, enum
Added in: Edition 2024
Default behavior per syntax/edition:
| Syntax/edition | Default |
|---|---|
| 2026 | false |
| 2024 | false |
| 2023 | false |
| proto3 | false |
| proto2 | false |
Note: Feature settings on different schema elements
have different scopes.
`features.(pb.cpp/pb.java).legacy_closed_enum` {#legacy_closed_enum}
Languages: C++, Java
This feature determines whether a field with an open enum type should be behave
as if it was a closed enum. This allows editions to reproduce
non-conformant behavior in
Java and C++ from proto2 and proto3.
This feature doesn't impact proto3 files, and so this section doesn't have a
before and after of a proto3 file.
Values available:
true: Treats the enum as closed regardless ofenum_type.false: Respect whatever is set in theenum_type.
Applicable to the following scopes: file, field
Added in: Edition 2023
Default behavior per syntax/edition:
| Syntax/edition | Default |
|---|---|
| 2026 | false |
| 2024 | false |
| 2023 | false |
| proto3 | false |
| proto2 | true |
Note: Feature settings on different schema elements
have different scopes.
The following code sample shows a proto2 file:
syntax = "proto2";
import "myproject/proto3file.proto";
message Msg {
myproject.proto3file.Proto3Enum name = 1;
}After running Prototiller, the equivalent code might look like this:
edition = "2024";
import "myproject/proto3file.proto";
import option "third_party/protobuf/cpp_features.proto";
import option "third_party/java/protobuf/java_features.proto";
message Msg {
myproject.proto3file.Proto3Enum name = 1 [
features.(pb.cpp).legacy_closed_enum = true,
features.(pb.java).legacy_closed_enum = true
];
}`features.(pb.java).nest_in_file_class` {#java-nest_in_file}
Languages: Java
This feature controls whether the Java generator will nest the generated class
in the Java generated file class. Setting this option to NO is the equivalent
of setting java_multiple_files = true in proto2/proto3/edition 2023.
The default outer classname is also updated to always be the camel-cased .proto
filename suffixed with Proto by default (for example, foo/bar_baz.proto
becomes BarBazProto). You can still override this using thejava_outer_classname file option and replace the pre-Edition 2024 default ofBarBaz or BarBazOuterClass depending on the presence of conflicts.
Values available:
NO: Do not nest the generated class in the file class.YES: Nest the generated class in the file class.- Legacy: Nesting behavior controlled by
java_multiple_filesoption (see
go/java-proto-names#immutable-api-message-names).
Applicable to the following scopes: message, enum, service
Added in: Edition 2024
Default behavior per syntax/edition:
| Syntax/edition | Default |
|---|---|
| 2026 | NO |
| 2024 | NO |
| 2023 | Legacy |
| proto3 | Legacy |
| proto2 | Legacy |
Note: Feature settings on different schema elements
have different scopes.
`features.(pb.cpp).string_type` {#string_type}
Languages: C++
This feature determines how generated code should treat string fields. This
replaces the ctype option from proto2 and proto3, and offers a newstring_type feature. In Edition 2023, you can specify either ctype orstring_type on a field, but not both. In Edition 2024, the ctype option is
removed.
Values available:
VIEW: Generatesstring_viewaccessors for the field.CORD: GeneratesCordaccessors for the field. Not supported on extension
fields.STRING: Generatesstringaccessors for the field.
Applicable to the following scopes: file, field
Added in: Edition 2023
Default behavior per syntax/edition:
| Syntax/edition | Default |
|---|---|
| 2026 | VIEW |
| 2024 | VIEW |
| 2023 | STRING |
| proto3 | STRING |
| proto2 | STRING |
Note: Feature settings on different schema elements
have different scopes.
The following code sample shows a proto2 file:
syntax = "proto2";
message Foo {
optional string bar = 6;
optional string baz = 7 [ctype = CORD];
}After running Prototiller, the equivalent code might look like this:
edition = "2024";
import option "third_party/protobuf/cpp_features.proto";
message Foo {
string bar = 6 [features.(pb.cpp).string_type = STRING];
string baz = 7 [features.(pb.cpp).string_type = CORD];
}The following shows a proto3 file:
syntax = "proto3"
message Foo {
string bar = 6;
string baz = 7 [ctype = CORD];
}After running Prototiller, the equivalent code might look like this:
edition = "2024";
import option "third_party/protobuf/cpp_features.proto";
message Foo {
string bar = 6 [features.(pb.cpp).string_type = STRING];
string baz = 7 [features.(pb.cpp).string_type = CORD];
}`features.(pb.java).utf8_validation` {#java-utf8_validation}
Languages: Java
This language-specific feature enables you to override the file-level settings
at the field level for Java only.
This feature doesn't impact proto3 files, and so this section doesn't have a
before and after of a proto3 file.
Values available:
DEFAULT: The behavior matches that set byfeatures.utf8_validation.VERIFY: Overrides the file-levelfeatures.utf8_validationsetting to
force it toVERIFYfor Java only.
Applicable to the following scopes: file, field
Added in: Edition 2023
Default behavior per syntax/edition:
| Syntax/edition | Default |
|---|---|
| 2026 | DEFAULT |
| 2024 | DEFAULT |
| 2023 | DEFAULT |
| proto3 | DEFAULT |
| proto2 | DEFAULT |
Note: Feature settings on different schema elements
have different scopes.
The following code sample shows a proto2 file:
syntax = "proto2";
option java_string_check_utf8=true;
message MyMessage {
string foo = 1;
string bar = 2;
}After running Prototiller, the equivalent code might look like this:
edition = "2024";
import "google/protobuf/java_features.proto";
option features.utf8_validation = NONE;
option features.(pb.java).utf8_validation = VERIFY;
message MyMessage {
string foo = 1;
string bar = 2;
}`features.(pb.go).strip_enum_prefix` {#go-strip_enum_prefix}
Languages: Go
Enum values are not scoped by their containing enum name, so
prefixing every value with the enum name is recommended:
edition = "2024";
enum Strip {
STRIP_ZERO = 0;
STRIP_ONE = 1;
}However, the generated Go code will now contain two prefixes!
type Strip int32
const (
Strip_STRIP_ZERO Strip = 0
Strip_STRIP_ONE Strip = 1
)The language-specific strip_enum_prefix feature determines whether the Go code
generator strips the repetitive prefix or not.
Values available:
STRIP_ENUM_PREFIX_KEEP: Keep the name as-is, even if repetitive.STRIP_ENUM_PREFIX_GENERATE_BOTH: Generate both, a full name and a stripped
name (to help with migrating your Go code).STRIP_ENUM_PREFIX_STRIP: Strip the enum name prefix from enum value names.
Applicable to the following scopes: file, enum, enum value
Added in: Edition 2024
Default behavior per syntax/edition:
| Syntax/edition | Default |
|---|---|
| 2026 | STRIP_ENUM_PREFIX_KEEP |
| 2024 | STRIP_ENUM_PREFIX_KEEP |
Note: Feature settings on different schema elements
have different scopes.
You can set the strip_enum_prefix feature in edition 2024 (or newer) .proto
files:
edition = "2024";
import "google/protobuf/go_features.proto";
option features.(pb.go).strip_enum_prefix = STRIP_ENUM_PREFIX_STRIP;
enum Strip {
STRIP_ZERO = 0;
STRIP_ONE = 1;
}The generated Go code will now strip the STRIP prefix:
type Strip int32
const (
Strip_ZERO Strip = 0
Strip_ONE Strip = 1
)Preserving proto2 or proto3 Behavior in Edition 2023 {#preserving}
You may want to move to the editions format but not deal with updates to the way
that generated code behaves yet. This section shows the changes that the
Prototiller tool makes to your .proto files to make edition-2023-based protos
behave like a proto2 or proto3 file.
After these changes are made at the file level, you get the proto2 or proto3
defaults. You can override at lower levels (message level, field level) to
consider additional behavior differences (such as
required, proto3 optional) or if you want your definition to only be
mostly like proto2 or proto3.
We recommend using Prototiller unless you have a specific reason not to. To
manually apply all of these instead of using Prototiller, add the content from
the following sections to the top of your .proto file.
Proto2 Behavior {#proto2-behavior}
The following shows the settings to replicate proto2 behavior with Edition 2023.
edition = "2023";
import "google/protobuf/cpp_features.proto";
import "google/protobuf/java_features.proto";
option features.field_presence = EXPLICIT;
option features.enum_type = CLOSED;
option features.repeated_field_encoding = EXPANDED;
option features.json_format = LEGACY_BEST_EFFORT;
option features.utf8_validation = NONE;
option features.(pb.cpp).legacy_closed_enum = true;
option features.(pb.java).legacy_closed_enum = true;Proto3 Behavior {#proto3-behavior}
The following shows the settings to replicate proto3 behavior with Edition 2023.
edition = "2023";
import "google/protobuf/cpp_features.proto";
import "google/protobuf/java_features.proto";
option features.field_presence = IMPLICIT;
option features.enum_type = OPEN;
// `packed=false` needs to be transformed to field-level repeated_field_encoding
// features in Editions syntax
option features.json_format = ALLOW;
option features.utf8_validation = VERIFY;
option features.(pb.cpp).legacy_closed_enum = false;
option features.(pb.java).legacy_closed_enum = false;Edition 2023 to 2024 {#2023-2024}
The following shows the settings to replicate Edition 2023 behavior with
Edition 2024.
// foo/bar_baz.proto
edition = "2024";
import option "third_party/protobuf/cpp_features.proto";
import option "third_party/java/protobuf/java_features.proto";
import option "third_party/golang/protobuf/v2/src/google/protobuf/go_features.proto";
// If previously relying on edition 2023 default java_outer_classname.
option java_outer_classname = "BarBaz" // or BarBazOuterClass
option features.(pb.cpp).string_type = STRING;
option features.enforce_naming_style = STYLE_LEGACY;
option features.default_symbol_visibility = EXPORT_ALL;
option features.(pb.cpp).enum_name_uses_string_view = false;
option features.(pb.go).api_level = API_OPEN;
message MyMessage {
option features.(pb.java).nest_in_file_class = YES;
}Edition 2024 to 2026 {#2024-2026}
The following shows the settings to replicate Edition 2024 behavior with
Edition 2026.
edition = "2026";
option features.enforce_naming_style = STYLE2024;
option features.default_symbol_visibility = EXPORT_TOP_LEVEL;Caveats and Exceptions {#caveats}
This section shows the changes that you'll need to make manually if you choose
not to use Prototiller.
Setting the file-level defaults shown in the previous section sets the default
behaviors in most cases, but there are a few exceptions.
Edition 2023 and later
optional: Remove all instances of theoptionallabel and change thefeatures.field_presencetoEXPLICITif the file
default isIMPLICIT.required: Remove all instances of therequiredlabel and add thefeatures.field_presence=LEGACY_REQUIREDoption at the
field level.groups: Unwrap thegroupsinto a separate message and add thefeatures.message_encoding = DELIMITEDoption at the field level. Seefeatures.message_encodingfor more on this.java_string_check_utf8: Remove this file option and replace it with thefeatures.(pb.java).utf8_validation. You'll need
to import Java features, as covered in
Language-specific Features.packed: For proto2 files converted to editions format, remove thepacked
field option and add[features.repeated_field_encoding=PACKED]at the
field level when you don't want theEXPANDEDbehavior that you set in
Proto2 Behavior. For proto3 files converted to editions
format, add[features.repeated_field_encoding=EXPANDED]at the field level
when you don't want the default proto3 behavior.
Edition 2024 and later
- (C++)
ctype: Remove all instances of thectypeoption and set thefeatures.(pb.cpp).string_typevalue. - (C++ and Go)
weak: Remove weak
imports. Useimport option
instead. - (Java)
java_multiple_files: Removejava_multiple_filesand usefeatures.(pb.java).nest_in_file_classinstead.
Edition 2026 and later
- (C++) Remove Language-specific options (
cc_api_version,cc_utf8_verification,cc_enable_arenas). - Set
features.enforce_proto_limitstoLEGACY_NO_EXPLICIT_LIMITSwithin- Messages that have either more than 1500 fields or more than 1000 oneofs
- Oneofs that have more than 1200 fields
- Enums that have more than 1700 values
File: content/editions/implementation.md
+++
title = "Implementing Editions Support"
weight = 43
description = "Instructions for implementing Editions support in runtimes and plugins."
type = "docs"
+++
This topic explains how to implement editions in new runtimes and generators.
Overview {#overview}
Edition 2023 {#edition-2023}
The first edition released is Edition 2023, which is designed to unify proto2
and proto3 syntax. The features weโve added to cover the difference in behaviors
are detailed in
Feature Settings for Editions.
Feature Definition {#feature-definition}
In addition to supporting editions and the global features we've defined, you
may want to define your own features to leverage the infrastructure. This will
allow you to define arbitrary features that can be used by your generators and
runtimes to gate new behaviors. The first step is to claim an extension number
for the FeatureSet message in descriptor.proto above 9999. You can send a
pull-request to us in GitHub, and it will be included in our next release (see,
for example, #15439).
Once you have your extension number, you can create your features proto (similar
to
cpp_features.proto).
These will typically look something like:
edition = "2023";
package foo;
import "google/protobuf/descriptor.proto";
extend google.protobuf.FeatureSet {
MyFeatures features = <extension #>;
}
message MyFeatures {
enum FeatureValue {
FEATURE_VALUE_UNKNOWN = 0;
VALUE1 = 1;
VALUE2 = 2;
}
FeatureValue feature_value = 1 [
targets = TARGET_TYPE_FIELD,
targets = TARGET_TYPE_FILE,
feature_support = {
edition_introduced: EDITION_2023,
edition_deprecated: EDITION_2024,
deprecation_warning: "Feature will be removed in 2025",
edition_removed: EDITION_2025,
},
edition_defaults = { edition: EDITION_LEGACY, value: "VALUE1" },
edition_defaults = { edition: EDITION_2024, value: "VALUE2" }
];
}Here weโve defined a new enum feature foo.feature_value (currently only
boolean and enum types are supported). In addition to defining the values it can
take, you also need to specify how it can be used:
- Targets - specifies the type of proto descriptors this feature can be
attached to. This controls where users can explicitly specify the feature.
Every type must be explicitly listed. - Feature support - specifies the lifetime of this feature relative to
edition. You must specify the edition it was introduced in, and it will not
be allowed before then. You can optionally deprecate or remove the feature
in later editions. - Edition defaults - specifies any changes to the default value of the
feature. This must cover every supported edition, but you can leave out any
edition where the default didnโt change. Note thatEDITION_PROTO2andEDITION_PROTO3can be specified here to provide defaults for the โlegacyโ
editions (see Legacy Editions).
What is a Feature? {#what-feature}
Features are designed to provide a mechanism for ratcheting down bad behavior
over time, on edition boundaries. While the timeline for actually removing a
feature may be years (or decades) in the future, the desired goal of any feature
should be eventual removal. When a bad behavior is identified, you can introduce
a new feature that guards the fix. In the next edition (or possibly later), you
would flip the default value while still allowing users to retain their old
behavior when upgrading. At some point in the future you would mark the feature
deprecated, which would trigger a custom warning to any users overriding it. In
a later edition, you would then mark it removed, preventing users from
overriding it anymore (but the default value would still apply). Until support
for that last edition is dropped in a breaking release, the feature will remain
usable for protos stuck on older editions, giving them time to migrate.
Flags that control optional behaviors you have no intention of removing are
better implemented as
custom options.
This is related to the reason weโve restricted features to be either boolean or
enum types. Any behavior controlled by a (relatively) unbounded number of values
probably isnโt a good fit for the editions framework, since itโs not realistic
to eventually turn down that many different behaviors.
One caveat to this is behaviors related to wire boundaries. Using
language-specific features to control serialization or parsing behavior can be
dangerous, since any other language could be on the other side. Wire-format
changes should always be controlled by global features in descriptor.proto,
which can be respected by every runtime uniformly.
Generators {#generators}
Generators written in C++ get a lot for free, because they use the C++ runtime.
They donโt need to handle Feature Resolution themselves,
and if they need any feature extensions they can register them inGetFeatureExtensions in their CodeGenerator. They can generally useGetResolvedSourceFeatures for access to resolved features for a descriptor in
codegen and GetUnresolvedSourceFeatures for access to their own unresolved
features.
Plugins written in the same language as the runtime they generate code for may
need some custom bootstrapping for their feature definitions.
Explicit Support {#explicit-support}
Generators must specify exactly which editions they support. This allows you to
safely add support for an edition after itโs been released, on your own
schedule. Protoc will reject any editions protos sent to generators that donโt
include FEATURE_SUPPORTS_EDITIONS in the supported_features field of theirCodeGeneratorResponse. Additionally, we have minimum_edition andmaximum_edition fields for specifying your precise support window. Once youโve
defined all of the code and feature changes for a new edition, you can bumpmaximum_edition to advertise this support.
Codegen Tests {#codegen-tests}
We have a set of codegen tests that can be used to lock down that Edition 2023
produces no unexpected functional changes. These have been very useful in
languages like C++ and Java, where a significant amount of the functionality is
in gencode. On the other hand, in languages like Python, where the gencode is
basically just a collection of serialized descriptors, these are not quite as
useful.
This infrastructure is not reusable yet, but is planned to be in a future
release. At that point you will be able to use them to verify that migrating to
editions doesnโt have any unexpected codegen changes.
Runtimes {#runtimes}
Runtimes without reflection or dynamic messages should not need to do anything
to implement Editions. All of that logic should be handled by the code
generator.
Languages with reflection but without dynamic messages need resolved
features, but may optionally choose to handle it in their generator only. This
can be done by passing both resolved and unresolved feature sets to the runtime
during codegen. This avoids re-implementing
Feature Resolution in the runtime with the main downside
being efficiency, since it will create a unique feature set for every
descriptor.
Languages with dynamic messages must fully implement Editions, because they need
to be able to build descriptors at runtime.
Syntax Reflection {#syntax_reflection}
The first step in implementing Editions in a runtime with reflection is to
remove all direct checks of the syntax keyword. All of these should be moved
to finer-grained feature helpers, which can continue to use syntax if
necessary.
The following feature helpers should be implemented on descriptors, with
language-appropriate naming:
FieldDescriptor::has_presence- Whether or not a field has explicit
presence- Repeated fields never have presence
- Message, extension, and oneof fields always have explicit presence
- Everything else has presence iff
field_presenceis notIMPLICIT
FieldDescriptor::is_required- Whether or not a field is requiredFieldDescriptor::requires_utf8_validation- Whether or not a field should
be checked for utf8 validityFieldDescriptor::is_packed- Whether or not a repeated field has packed
encodingFieldDescriptor::is_delimited- Whether or not a message field has
delimited encodingEnumDescriptor::is_closed- Whether or not a field is closed
{{% alert title="Note" color="note" %}} In
most languages, the message encoding feature is still currently signaled byTYPE_GROUP and required fields still have LABEL_REQUIRED set. This is not
ideal, and was done to make downstream migrations easier. Eventually, these
should be migrated to the appropriate helpers andTYPE_MESSAGE/LABEL_OPTIONAL.{{% /alert %}}
Downstream users should migrate to these new helpers instead of using syntax
directly. The following class of existing descriptor APIs should ideally be
deprecated and eventually removed, since they leak syntax information:
FileDescriptorsyntax- Proto3 optional APIs
FieldDescriptor::has_optional_keywordOneofDescriptor::is_syntheticDescriptor::*real_oneof*- should be renamed to just โoneofโ and the
existing โoneofโ helpers should be removed, since they leak information
about synthetic oneofs (which donโt exist in editions).
- Group type
- The
TYPE_GROUPenum value should be removed, replaced with theis_delimitedhelper.
- The
- Required label
- The
LABEL_REQUIREDenum value should be removed, replaced with theis_requiredhelper.
- The
There are many classes of user code where these checks exist but arenโt
hostile to editions. For example, code that needs to handle proto3 optional
specially because of its synthetic oneof implementation wonโt be hostile to
editions as long as the polarity is something like syntax == "proto3" (rather
than checking syntax != "proto2").
If itโs not possible to remove these APIs entirely, they should be deprecated
and discouraged.
Feature Visibility {#visibility}
As discussed in
editions-feature-visibility,
feature protos should remain an internal detail of any Protobuf implementation.
The behaviors they control should be exposed via descriptor methods, but the
protos themselves should not. Notably, this means that any options that are
exposed to the users need to have their features fields stripped out.
The one case where we permit features to leak out is when serializing
descriptors. The resulting descriptor protos should be a faithful representation
of the original proto files, and should contain unresolved features inside of
the options.
Legacy Editions {#legacy_editions}
As discussed more in
legacy-syntax-editions,
a great way to get early coverage of your editions implementation is to unify
proto2, proto3, and editions. This effectively migrates proto2 and proto3 to
editions under the hood, and makes all of the helpers implemented in
Syntax Reflection use features exclusively (instead of
branching on syntax). This can be done by inserting a feature inference phase
into Feature Resolution, where various aspects of the
proto file can inform what features are appropriate. These features can then be
merged into the parentโs features to get the resolved feature set.
While we provide reasonable defaults for proto2/proto3 already, for edition 2023
the following additional inferences are required:
- required - we infer
LEGACY_REQUIREDpresence when a field hasLABEL_REQUIRED - groups - we infer
DELIMITEDmessage encoding when a field hasTYPE_GROUP - packed - we infer
PACKEDencoding when thepackedoption is true - expanded - we infer
EXPANDEDencoding when a proto3 field haspacked
explicitly set to false
Conformance Tests {#conformance-tests}
Editions-specific conformance tests have been added, but need to be opted-in to.
A --maximum_edition 2023 flag can be passed to the runner to enable these. You
will need to configure your testee binary to handle the following new message
types:
protobuf_test_messages.editions.proto2.TestAllTypesProto2- Identical to
the old proto2 message, but transformed to edition 2023protobuf_test_messages.editions.proto3.TestAllTypesProto3- Identical to
the old proto3 message, but transformed to edition 2023protobuf_test_messages.editions.TestAllTypesEdition2023- Used to cover
edition-2023-specific test cases
Feature Resolution {#feature-resolution}
Editions use lexical scoping to define features, meaning that any non-C++ code
that needs to implement editions support will need to reimplement our feature
resolution algorithm. However, the bulk of the work is handled by protoc
itself, which can be configured to output an intermediate FeatureSetDefaults
message. This message contains a โcompilationโ of a set of feature definition
files, laying out the default feature values in every edition.
For example, the feature definition above would compile to the following
defaults between proto2 and edition 2025 (in text-format notation):
defaults {
edition: EDITION_PROTO2
overridable_features { [foo.features] {} }
fixed_features {
// Global feature defaultsโฆ
[foo.features] { feature_value: VALUE1 }
}
}
defaults {
edition: EDITION_PROTO3
overridable_features { [foo.features] {} }
fixed_features {
// Global feature defaultsโฆ
[foo.features] { feature_value: VALUE1 }
}
}
defaults {
edition: EDITION_2023
overridable_features {
// Global feature defaultsโฆ
[foo.features] { feature_value: VALUE1 }
}
}
defaults {
edition: EDITION_2024
overridable_features {
// Global feature defaultsโฆ
[foo.features] { feature_value: VALUE2 }
}
}
defaults {
edition: EDITION_2025
overridable_features {
// Global feature defaultsโฆ
}
fixed_features { [foo.features] { feature_value: VALUE2 } }
}
minimum_edition: EDITION_PROTO2
maximum_edition: EDITION_2025Global feature defaults are left out for compactness, but they would also be
present. This object contains an ordered list of every edition with a unique set
of defaults (some editions may end up not being present) within the specified
range. Each set of defaults is split into overridable and fixed features.
The former are supported features for the edition that can be freely overridden
by users. The fixed features are those which havenโt yet been introduced or have
been removed, and canโt be overridden by users.
We provide a Bazel rule for compiling these intermediate objects:
load("@com_google_protobuf//editions:defaults.bzl", "compile_edition_defaults")
compile_edition_defaults(
name = "my_defaults",
srcs = ["//some/path:lang_features_proto"],
maximum_edition = "PROTO2",
minimum_edition = "2024",
)The output FeatureSetDefaults can be embedded into a raw string literal in
whatever language you need to do feature resolution in. We also provide anembed_edition_defaults macro to do this:
embed_edition_defaults(
name = "embed_my_defaults",
defaults = ":my_defaults",
output = "my_defaults.h",
placeholder = "DEFAULTS_DATA",
template = "my_defaults.h.template",
)Alternatively, you can invoke protoc directly (outside of Bazel) to generate
this data:
protoc --edition_defaults_out=defaults.binpb --edition_defaults_minimum=PROTO2 --edition_defaults_maximum=2023 <feature files...>Once the defaults message is hooked up and parsed by your code, feature
resolution for a file descriptor at a given edition follows a simple algorithm:
- Validate that the edition is in the appropriate range [
minimum_edition,maximum_edition] - Binary-search the ordered
defaultsfield for the highest entry less than
or equal to the edition - Merge
overridable_featuresintofixed_featuresfrom the selected
defaults - Merge any explicit features set on the descriptor (the
featuresfield in
the file options)
From there, you can recursively resolve features for all other descriptors:
- Initialize to the parent descriptorโs feature set
- Merge any explicit features set on the descriptor (the
featuresfield in
the options)
For determining the โparentโ descriptor, you can reference our
C++ implementation.
This is straightforward in most cases, but extensions are a bit surprising
because their parent is the enclosing scope rather than the extendee. Oneofs
also need to be considered as the parent of their fields.
Conformance Tests {#conformance-tests}
In a future release, we plan to add conformance tests to verify feature
resolution cross-language. Until then, our regular
conformance tests do give partial coverage, and our
example inheritance unit tests
can be ported to provide more comprehensive coverage.
Examples {#examples}
Below are some real examples of how we implemented editions support in our
runtimes and plugins.
Java {#java}
- #14138 - Bootstrap
compiler with C++ gencode for Java features proto - #14377 - Use
features in Java, Kotlin, and Java Lite code generators, including codegen
tests - #15210 - Use
features in Java full runtimes covering Java features bootstrap, feature
resolution, and legacy editions, along with unit-tests and conformance
testing
Pure Python {#python}
- #14546 - Setup
codegen tests in advance - #14547 - Fully
implements editions in one shot, along with unit-tests and conformance
testing
๐pb {#upb}
- #14638 - First
pass at editions implementation covering feature resolution and legacy
editions - #14667 - Added
more complete handling of field label/type, support for upbโs code
generator, and some tests - #14678 - Hooks up
upb to the Python runtime, with more unit tests and conformance tests
Ruby {#ruby}
- #16132 - Hook up
upb/Java to all four Ruby runtimes for full editions support