Protobuf Scalar Field Types
Protocol Buffers defines 15 scalar types. Each maps to a specific wire type and native language type. Choosing the right type affects wire size, language mapping, and serialization correctness.
15
Types
4
Wire Types
All Scalar Types
| Type | Wire Type | Default | C++ | Java | Go | |
|---|---|---|---|---|---|---|
| double | 1 64-bit | 0.0 | double | double | float64 | |
| float | 5 32-bit | 0.0 | float | float | float32 | |
| int32 | 0 Varint | 0 | int32_t | int | int32 | |
| int64 | 0 Varint | 0 | int64_t | long | int64 | |
| uint32 | 0 Varint | 0 | uint32_t | int (unsigned semantics) | uint32 | |
| uint64 | 0 Varint | 0 | uint64_t | long (unsigned semantics) | uint64 | |
| sint32 | 0 Varint | 0 | int32_t | int | int32 | |
| sint64 | 0 Varint | 0 | int64_t | long | int64 | |
| bool | 0 Varint | false | bool | boolean | bool | |
| string | 2 Len-delim | "" | std::string | String | string | |
| bytes | 2 Len-delim | "" | std::string | ByteString | []byte | |
| fixed32 | 5 32-bit | 0 | uint32_t | int (unsigned semantics) | uint32 | |
| fixed64 | 1 64-bit | 0 | uint64_t | long (unsigned semantics) | uint64 | |
| sfixed32 | 5 32-bit | 0 | int32_t | int | int32 | |
| sfixed64 | 1 64-bit | 0 | int64_t | long | int64 |
Floating-Point
IEEE 754 values encoded at fixed size
double is a 64-bit IEEE 754 floating-point field. Uses wire type 1 (8 bytes little-endian). Default value is 0.0. Equivalent to double in C++, double in Java, float64 in Go.
8 bytes always – no size advantage over varint for integer-valued doubles
float is a 32-bit IEEE 754 floating-point field. Uses wire type 5 (4 bytes little-endian). Default value is 0.0. Equivalent to float in C++, float in Java, float32 in Go.
4 bytes always – half the size of double
Signed Integers (varint)
Negative values always take 10 bytes – use sint32/64 instead
int32 is a signed 32-bit integer using varint encoding. Negative values always take 10 bytes (encoded as if 64-bit). Use sint32 for fields expected to carry negative numbers. Default value is 0.
Negative values always encode as 10 bytes – use sint32 for fields that can be negative
int64 is a signed 64-bit integer using varint encoding. Like int32, negative values always take 10 bytes. Use sint64 for fields that regularly carry negative numbers. Default value is 0.
Negative values always encode as 10 bytes – use sint64 for negative-capable fields
Unsigned Integers (varint)
No negative penalty – compact for all non-negative values
uint32 is an unsigned 32-bit integer using varint encoding. Since it is always non-negative, there is no 10-byte penalty. Use for counts, sizes, port numbers, and other non-negative integers. Default value is 0.
Always non-negative – no 10-byte varint penalty
uint64 is an unsigned 64-bit integer using varint encoding. Always non-negative. Used for large counters, file sizes, snowflake IDs, and bit fields. Default value is 0.
Range: 0 to 2^64-1
Signed Integers (zigzag)
Compact encoding for negative values – prefer over int32/64
sint32 is a signed 32-bit integer using zigzag varint encoding. Zigzag maps negative integers to positive integers (n ≥ 0 → 2n, n < 0 → 2(-n)-1) so small negative values encode compactly. Use sint32 instead of int32 when a field regularly holds negative values.
Always prefer sint32 over int32 when field values can be negative
sint64 is a signed 64-bit integer using zigzag varint encoding. Like sint32 but 64-bit range. Use instead of int64 when a field regularly holds negative values. Default value is 0.
Always prefer sint64 over int64 when field values can be negative
Fixed-Size Unsigned
Efficient when values consistently exceed 2^28 (32-bit) or 2^56 (64-bit)
fixed32 is an unsigned 32-bit integer always encoded as exactly 4 bytes little-endian (wire type 5). More efficient than uint32 for values consistently above 2^28 (≈268 million). Default value is 0.
Always 4 bytes – efficient when values regularly exceed 2^28
fixed64 is an unsigned 64-bit integer always encoded as exactly 8 bytes little-endian (wire type 1). More efficient than uint64 for values consistently above 2^56. Default value is 0.
Always 8 bytes – efficient when values regularly exceed 2^56
Fixed-Size Signed
Fixed 4/8 bytes – avoids 10-byte varint penalty for large negative values
sfixed32 is a signed 32-bit integer always encoded as exactly 4 bytes little-endian (wire type 5). Two's complement encoding. Avoids the 10-byte penalty of int32 for large negative values when fixed size is acceptable. Default value is 0.
Always 4 bytes – no varint overhead, no 10-byte negative penalty
sfixed64 is a signed 64-bit integer always encoded as exactly 8 bytes little-endian (wire type 1). Two's complement encoding. Avoids the 10-byte varint penalty for large negative int64 values when fixed 8-byte size is acceptable. Default value is 0.
Always 8 bytes – predictable wire size, no negative penalty
Byte Strings
Length-delimited: varint byte count + payload
string is a UTF-8 encoded text field using wire type 2 (length-delimited). Encoded as a varint byte count followed by the UTF-8 bytes. Default value is empty string. Must contain valid UTF-8. Use bytes for arbitrary binary data.
Must be valid UTF-8 – use bytes for arbitrary binary data
bytes is an arbitrary binary data field using wire type 2. Encoded as a varint byte count followed by raw bytes. No encoding constraint – any byte sequence is valid. Default value is empty bytes. Use for cryptographic material, compressed data, or opaque payloads.
No UTF-8 constraint – any byte sequence is valid
Wire Type Legend
Tag encoding: tag = (field_number << 3) | wire_type. Field 1, double = 0x09. Source: protobuf.dev encoding guide