English
Protobuf Data Types Explained: A Practical Guide to enum, repeated, map, and oneof

Introduction: Why are data types important?
Choosing the wrong data type can waste bandwidth in minor cases and cause compatibility issues in severe cases.
In a Code Review, I found an interesting case: a field clearly only needed to store numbers from 0 to 100, yet it used int64; another field needs to store binary data, but used string. Even more absurdly, someone wrote in an enum kPreLoad = 1, the enum value surprisingly doesn't start from 0.
These seemingly minor choices become difficult to change once the Proto file goes live—field numbers cannot be changed, and the client and server need to be upgraded in sync, at great cost.
This article summarizes a Protobuf data type usage guide based on observations from this Code Review. We hope it helps you choose correctly the first time and avoid pitfalls.
Statement: Pick the right type, and you’ll have half as many bugs.
Choosing data types is the first step in designing a Proto file. If you choose the wrong types, all subsequent code will go wrong accordingly. This is not a trivial matter—it is a key step that determines interface quality.
1. Scalar Types Quick Reference
Protobuf provides a rich set of scalar types; when choosing a type, consider:Storage space, performance, language compatibility.
1.1 Integer Types
| Type | Storage space | Applicable scenarios |
|---|---|---|
int32 | Variable length (1-5 bytes) | In most scenarios, negative number encoding is inefficient. |
int64 | Variable length (1-9 bytes) | Large numbers or timestamps |
uint32 | Variable length (1–5 bytes) | non-negative numbers |
uint64 | Variable length (1-9 bytes) | non-negative large numbers |
sint32 | Variable length (1-5 bytes) | Scenarios with many negative numbers (ZigZag encoding) |
sint64 | Variable length (1-9 bytes) | Scenarios with many negative numbers (ZigZag encoding) |
fixed32 | 4 bytes | More space-efficient when the value > 2^28 |
fixed64 | 8 bytes | More space-efficient when the value > 2^56 |
Selection Recommendations
Most scenarios: use
int32时间戳: use
int64ID/Count/Enumeration: use
uint32(non-negative)There are many negative numbers.: Use
sint32/sint64
Practical case
protobuf
// ✅ Correct example
message UserInfo {
uint32 user_id = 1; // ID uses uint32
uint32 user_num = 2; // Use uint32 for the ID
int64 created_at = 3; // Timestamp uses int64
int32 status = 4; // State uses int32
}
// ❌ Incorrect example
message UserInfo {
string user_id = 1; // Using strings for IDs wastes space.
int32 created_at = 2; // Timestamps use int32 and will overflow in 2038!
}1.2 String Type
| Type | Description | Applicable scenarios |
|---|---|---|
string | UTF-8 encoded text | Plain text, JSON, XML |
bytes | binary data | File contents, encrypted data, serialized data |
Selection advice
text data: use
stringbinary data: use
bytesUncertain: use
bytes(Safer, better compatibility)
Practical case studies
protobuf
// ✅ Correct example
message FileInfo {
string file_name = 1; // Use string for the filename
bytes file_content = 2; // Use bytes for the file content
string checksum = 3; // Checksum (hex string) uses string
}
// ❌ Incorrect example
message FileInfo {
bytes file_name = 1; // Using bytes for the filename is unreasonable.
string file_content = 2; // Use string for binary content; it may be garbled.
}II. Enumeration (enum)
2.1 Basic Usage
Enums are used to represent a fixed set of values.
Specification requirements:
Enumeration values must start from 0 Start
Each enumeration value must have a clear meaning.
It is recommended that the first enumeration value be
XXX_UNSPECIFIED = 0
Correct example
protobuf
enum RoleType {
ROLE_TYPE_UNSPECIFIED = 0;
ROLE_TYPE_ADMIN = 1;
ROLE_TYPE_GUEST = 2;
}
enum Status {
STATUS_UNSPECIFIED = 0;
STATUS_ACTIVE = 1;
STATUS_INACTIVE = 2;
}Error example
protobuf
enum RoleType {
ADMIN = 1; // Don't start from 0!
GUEST = 2;
}
enum RoleType {
ADMIN = 0; // Starting from 0, but without UNSPECIFIED
GUEST = 1;
}2.2 Enumeration values must start from 0
Why?
In Proto3, enum value 0 is the default value.
If the field is not set, 0 will be returned
If 0 is not defined, it will cause parsing to fail.
2.3 Naming Prefixes for Enumeration Values
Avoid value name conflicts between different enums:
protobuf
enum RoleType {
ROLE_TYPE_UNSPECIFIED = 0;
ROLE_TYPE_ADMIN = 1;
ROLE_TYPE_GUEST = 2;
}
enum TaskType {
TASK_TYPE_UNSPECIFIED = 0;
TASK_TYPE_IMPORT = 1;
TASK_TYPE_EXPORT = 2;
}2.4 Nested Enumerations
Enums can be nested in message Internal, for use only within the current Message:
protobuf
message AddUserRequest {
enum RoleType {
ROLE_TYPE_UNSPECIFIED = 0;
ROLE_TYPE_ADMIN = 1;
ROLE_TYPE_GUEST = 2;
}
RoleType role_type = 1;
}When using it, you need to reference the parent:AddUserRequest.RoleType
III. Composite Types
3.1 repeated: array/list
repeated Indicates that a field can contain multiple values (similar to an array).
Specification requirements:
Field name usage plural form(
user_ids,paths)Avoid excessively large repeated fields (which may cause performance issues)
Correct example
protobuf
message UserListResponse {
repeated UserInfo users = 1; // Plural naming
repeated string user_ids = 2;
}
message MultiPathRequest {
repeated string paths = 1;
}Error example
protobuf
message UserListResponse {
repeated UserInfo user = 1; // Singular naming is prone to misunderstanding.
repeated string user_id = 2;
}Practical Case Studies
protobuf
message ListUsersResponse {
CommonResponse result = 1;
repeated UserInfo user_list = 2; // Clear list naming
uint32 total_count = 3; // Total
}3.2 map: dictionary/mapping
map<key, value> Represents a collection of key-value pairs.
Specification requirements:
Key can only be integer or string Type
Value can be any type (including message)
Field name usage singular form(
user_map,config_map)
Correct example
protobuf
message TaskListResponse {
map<uint64, TaskInfo> task_map = 1; // key is ID, value is details
}
message ConfigResponse {
map<string, string> configs = 1; // key is the configuration name, value is the value
}Practical Cases
protobuf
message ListTaskResponse {
CommonResponse result = 1;
map<uint64, TaskInfo> task_map = 2; // Use map to directly index task details
}Choosing between map and repeated
| Scene | Recommend |
|---|---|
| Need fast lookup by key | map |
| Traverse in order | repeated |
| Need to deduplicate | map |
| Simple list | repeated |
IV. Nested Types
4.1 Internal enum
Enumeration nested in message Internal:
protobuf
message AddUserRequest {
enum RoleType {
ROLE_TYPE_UNSPECIFIED = 0;
ROLE_TYPE_ADMIN = 1;
ROLE_TYPE_GUEST = 2;
}
RoleType role_type = 1;
}Reference the parent when using:
go
req := &pb.AddUserRequest{
RoleType: pb.AddUserRequest_ROLE_TYPE_ADMIN,
}4.2 Internal message
Define the helper type in the parent:
protobuf
message TeamInfo {
uint32 team_id = 1;
message MemberInfo {
string role = 1;
string member_id = 2;
}
MemberInfo first_member = 2;
MemberInfo second_member = 3;
}Reference the parent when used:
go
info := &pb.TeamInfo{
FirstMember: &pb.TeamInfo_MemberInfo{
Role: "leader",
MemberId: "member-001",
},
}4.3 Reasonable Boundaries of Nesting
| Nested type | Suggestion |
|---|---|
| internal enum | ✅ When an enum is only used within the parent, nesting is recommended. |
| internal message | ⚠️ Use sparingly unless it is a strongly dependent auxiliary type. |
| Service nesting | ❌Prohibited, must be defined at the top level |
| Request/Response nesting | ❌Prohibited, must be defined at the top level |
V. Special Types
5.1 optional: optional field
In Proto3, all fields are by default Optional of (returns the default value when not set).optional can let you explicit Flag whether the field is set.
Use cases
protobuf
message UpdateUserRequest {
string user_id = 1;
optional string user_name = 2; // Optional field, can be used to check whether it is set
optional int64 update_time = 3; // Optional field, can be used to check whether it is set
}optional vs default value
go
// When using optional, you can determine whether a field is set.
if req.UserName != nil {
// The user explicitly set user_name
}
// Without optional, it is impossible to distinguish between "set to an empty string" and "not set".
// The default value "" will be considered as "not set"5.2 bytes: binary data
bytes Used to store arbitrary binary data:
protobuf
message FileChunkResponse {
bytes chunk = 1; // File chunk data
}
message SecureGatewayRequest {
bytes private_key = 1; // Private key (binary)
bytes public_key = 2; // Public key (binary)
}bytes vs string
| Type | Applicable scenarios |
|---|---|
string | UTF-8 text, JSON, XML |
bytes | Binary data (files, encrypted, compressed data) |
VI. Public Type Design
6.1 Unified Response Structure
Extract the common response structure into a separate common.proto Chinese:
protobuf
// common.proto
message CommonResponse {
int32 code = 1;
string message = 2;
}6.2 Import References
protobuf
// user.proto
import "common.proto";
message CreateUserResponse {
CommonResponse result = 1;
UserInfo user = 2;
}VII. Type Selection Quick Reference Table
| Scene | Recommendation type | Explanation |
|---|---|---|
| ID / Number | uint32/uint64 | non-negative integer |
| timestamp | int64 | Avoid the 2038 overflow |
| Count / Quantity | uint64 | May exceed 2^32 |
| Status code | int32 | The range is small, int32 is sufficient. |
| text | string | UTF-8 |
| binary | bytes | arbitrary data |
| Fixed value set | enum | Highly readable |
| list | repeated | array |
| key-value pair | map | Quick search |
| Optional field | optional | Explicit flag not set |
Summary
Data type selection is the cornerstone of Proto design. If you choose wrong, compatibility issues will follow the entire project lifecycle. Once launched, field numbers and types cannot be easily changed.
Three core principles:
Choose the appropriate integer type: No wasted space, no overflow
Enumeration starts from 0: Proto3 default value requirements
Nesting should be moderate.: Internal enum is allowed; Request/Response and Service must not be nested
Finally, I’d like to share a sentence with everyone:Choose the right types and you halve the bugs; choose the wrong types and the refactoring never ends.
Further reading
This is the second article in the Protobuf series.
📖 Previous article:Protobuf Naming Convention Best Practices: The Importance of Naming Conventions as Seen from Code Reviews of 50+ Proto Files
📖 Next article:Protobuf File Organization Guidelines: A Practical Guide to Splitting, Imports, and Common Type Management