- Published on
Mastering gRPC Encoding: An In-Depth Look at Protobuf and HTTP/2
- Authors

- Name
- Mehdi Akiki
Let's dive deep into how the encoding happens with Protocol Buffers (Protobuf) in gRPC, focusing on the binary format and the structure of the message exchanged between client and server.
Overview of gRPC Message Flow
When using gRPC, data is serialized using Protocol Buffers (Protobuf). The data is then transmitted over HTTP/2. We'll use the same GetUser request from the example above, where a client requests a user by ID, and the server responds with the user's details (ID, name, email).
Protobuf Message Structure
Protobuf serializes data into a binary format for efficiency, which means that the data sent over the network is much smaller and faster to parse than text formats like JSON or XML.
Step-by-Step: Protobuf Encoding
Let’s break down how Protobuf encoding works using the UserRequest and UserResponse messages from our .proto file.
Protobuf Serialization (Encoding)
Protobuf messages consist of fields, each identified by a field number and encoded using a compact binary representation. The encoding is efficient and reduces the size of the serialized data.
Protobuf's encoding basics
- Field number: Defined in the .proto file (e.g., id = 1, name = 2, email = 3).
- Wire type: Determines how the data is encoded (e.g., varint for integers, length-delimited for strings).
- Tag: The combination of the field number and the wire type, which is packed into a single byte or more.
- Value: The actual data encoded based on the wire type.
The binary encoding format consists of:
- A key (tag), which includes the field number and wire type.
- The value, serialized according to the wire type.
Let’s see how this works for the UserRequest and UserResponse messages.
Example: UserRequest Encoding
message UserRequest {
int32 id = 1;
}
For a request where id = 1234, here’s how it’s encoded:
- Field number: 1 (for id)
- Wire type: 0 (for int32, which is encoded as a varint)
- Tag: This is the combination of the field number and the wire type. For the
idfield:- The tag is encoded as:
1 << 3 | 0→0x08(in hexadecimal).
- The tag is encoded as:
Now, let's break it down:
- Tag: 0x08 (1 for the field number, 0 for the wire type).
- Value: 1234 is encoded as a varint:
1234in binary is10011010010- In Protobuf varint encoding, this is split into 7-bit chunks and encoded as:
0xd2 0x09.
Final binary message for UserRequest{id: 1234}:
0x08 0xd2 0x09
Explanation:
0x08: The tag (field number 1, wire type 0 for varint).0xd2 0x09: The varint-encoded value for1234.
Example: UserResponse Encoding
message UserResponse {
int32 id = 1;
string name = 2;
string email = 3;
}
For a response where:
id = 1234name = "Alice"email = "[email protected]"
Field 1 (id):
- Field number:
1 - Wire type:
0 (varint for int32) - Tag:
0x08 - Value:
0xd2 0x09 (same as before for 1234)
Field 2 (name):
- Field number: 2`
- Wire type:
2 (length-delimited for string) - Tag:
0x12 (2 << 3 | 2) - Length of the string:
5 (since "Alice" is 5 bytes) - Value: ASCII encoding of "Alice" is:
41 6c 69 63 65
Field 3 (email):
- Field number:
3 - Wire type:
2(length-delimited for string) - Tag:
0x1a(3 << 3 | 2) - Length of the string:
17(since "[email protected]" is 17 bytes) - Value: ASCII encoding of "[email protected]" is:
61 6c 69 63 65 40 65 78 61 6d 70 6c 65 2e 63 6f 6d
Now, let’s combine all of these into the final binary message:
- id = 1234:
0x08 0xd2 0x09 - name = "Alice":
0x12 0x05 0x41 0x6c 0x69 0x63 0x65 - email = "[email protected]":
0x1a 0x11 0x61 0x6c 0x69 0x63 0x65 0x40 0x65 0x78 0x61 0x6d 0x70 0x6c 0x65 0x2e 0x63 0x6f 0x6d
Final binary message for UserResponse:
0x08 0xd2 0x09
0x12 0x05 0x41 0x6c 0x69 0x63 0x65
0x1a 0x11 0x61 0x6c 0x69 0x63 0x65 0x40 0x65 0x78 0x61 0x6d 0x70 0x6c 0x65 0x2e 0x63 0x6f 0x6d
Packet Structure in gRPC
The binary-encoded Protobuf messages are packed into HTTP/2 frames when transmitted using gRPC. Here’s the structure of the gRPC packet over HTTP/2:
gRPC Header (5 bytes):
- 1 byte for compression flag: Indicates if the message is compressed (0 = no compression).
- 4 bytes for message length: Length of the Protobuf-encoded message (32-bit unsigned integer).
gRPC Payload:
The binary-encoded Protobuf message.
Example Packet:
Let’s assume the serialized message for UserResponse is 36 bytes long (since we calculated it earlier). The gRPC packet would look like this:
gRPC Header:
- 1 byte for compression: 0x00 (no compression).
- 4 bytes for message length: 0x00 0x00 0x00 0x24 (36 in hex is 0x24).
And Protobuf Payload (as calculated above):
0x08 0xd2 0x09
0x12 0x05 0x41 0x6c 0x69 0x63 0x65
0x1a 0x11 0x61 0x6c 0x69 0x63 0x65 0x40 0x65 0x78 0x61 0x6d 0x70 0x6c 0x65 0x2e 0x63 0x6f 0x6d
The full gRPC packet sent over HTTP/2 would be:
0x00 0x00 0x00 0x00 0x24
0x08 0xd2 0x09
0x12 0x05 0x41 0x6c 0x69 0x63 0x65
0x1a 0x11 0x61 0x6c 0x69 0x63 0x65 0x40 0x65 0x78 0x61 0x6d 0x70 0x6c 0x65 0x2e 0x63 0x6f 0x6d
- gRPC Header: 5 bytes (0x00 0x00 0x00 0x00 0x24)
- Protobuf Payload: 36 bytes (Protobuf-encoded UserResponse message).
Schema of Packet Encoding (ASCII Representation)
Here’s a simple ASCII representation of the gRPC message structure:
+----------------------------------------+
| gRPC Header (5 bytes) |
|----------------------------------------|
| Compression Flag (1 byte) |
| Message Length (4 bytes) |
+----------------------------------------+
| Protobuf Payload (binary data) |
|----------------------------------------|
| Tag (Field Number + Wire Type) |
| Field Value (encoded based on type) |
+----------------------------------------+
For example, a UserResponse message containing id, name, and email would be encoded as:
+----------------------------------------+
| 0x00 | (no compression) |
| 0x00 0x00 0x00 0x24 | (36 bytes length)|
+----------------------------------------+
| 0x08 0xd2 0x09 | (id: 1234) |
| 0x12 0x05 0x41 0x6c 0x69 0x63 0x65 | |
| (name: "Alice") |
| 0x1a 0x11 0x61 0x6c ... 0x6f 0x6d | |
| (email: "[email protected]") |
+----------------------------------------+
This schema and binary encoding layout highlight the efficiency of gRPC for transmitting data over the network.
Summary
gRPC’s combination of Protocol Buffers (Protobuf) for serialization and HTTP/2 for transport makes it an extremely efficient system for client-server communication. Protobuf’s binary format reduces the size of messages significantly, ensuring that data can be serialized and transmitted quickly compared to text-based formats like JSON. Each field in a Protobuf message is encoded compactly with a tag (combining the field number and wire type) followed by the binary-encoded value, allowing gRPC to handle complex data structures with minimal overhead.
gRPC also leverages the advanced capabilities of HTTP/2, such as multiplexing and bidirectional streaming, to further enhance communication speed and concurrency. Each gRPC message contains a fixed 5-byte header that indicates compression and message length, followed by the serialized Protobuf payload. This compact design ensures that gRPC can handle high-performance, real-time communication between distributed services, making it an ideal solution for modern microservices architectures.
I build and scale reliable production systems. Open to full-time and freelance work with U.S.-based teams that value ownership and execution.
Got something in mind?
Book a Discovery Call