A 2-Field Protobuf Message Generated Nearly 1000 Lines of Java. Why?
A 2-Field Protobuf Message Generated Nearly 1000 Lines of Java. Why?

We all know the usual sales pitch for Protocol Buffers.
- Faster than JSON
- Smaller payloads
- Efficient network usage
- Used by gRPC
- Battle-tested at Google
You’ve probably heard all of that before. What surprised me wasn’t the benchmarks. It was this:
A protobuf message with just two fields generated nearly a thousand lines of Java code 🤯.
message User {
int64 id = 1;
string name = 2;
}
Why would something so simple need so much code? The answer reveals something fundamental about how Protobuf works.
It is not just a binary format. It is a different way of thinking about serialization itself.
How JSON-Based Systems Work
Consider a simple Java class:
public class User {
private int id;
private String name;
private String email;
}
In a traditional REST API, we rely on two components:
Serializer: Converts an object into JSON.
Deserializer: Converts JSON back into a Java object.
At first glance this seems straightforward. But look carefully at what is being transmitted.
{
"id": 1234,
"name": "Chandler",
"email": "chandler@friends.show"
}
- The receiving system already has a
Userclass. - It already knows there is an
id. - It already knows there is a
name. - It already knows there is an
email.
Yet every request sends those field names again.
Why JSON Requires More Work
JSON is not slow because it is text.
The real issue is that the parser has to rediscover information that already exists in your application.
When a JSON parser receives:
{
"id": 1234,
"name": "Chandler"
}
it must answer several questions at runtime:
- What fields exist?
- Which Java property does “id”, “name” etc map to?
- What type should each value become?
- Is the value valid?
Even modern libraries like Jackson cache much of this metadata, but the work still starts from runtime schema discovery.
In addition:
- Field names are transmitted repeatedly.
- Numbers are converted to text and back again.
- Parsers must process every character individually.
- More temporary objects are created during parsing.
None of these costs are enormous on their own. But they occur for every message.
Protobuf Starts With a Different Assumption
Protobuf asks a simple question:
What if both sides already know the schema?
Instead of transmitting:
{
"id": 1234,
"name": "Chandler"
}
we define a contract ahead of time:
message User {
int64 id = 1;
string name = 2;
}
Now both systems know:
Field 1 -> id
Field 2 -> name
Those names never need to travel across the network again. The schema has already captured that knowledge.
And that is exactly why the protobuf compiler generates so much code. It is moving work from runtime to compile time.
The generated code already knows:
- field numbers
- field types
- serialization rules
- parsing rules
- presence tracking
- size calculations
Instead of discovering all of that during every request, Protobuf generates specialized code once and reuses it forever.
The 1000 lines of generated Java are not accidental. They are the reason protobuf can be so efficient. Let’s see an example
How Protobuf Serializes/Deserializes So Quickly
Consider the generated serialization code:
@Override
public void writeTo(CodedOutputStream output)
throws IOException {
if (((bitField0_ & 0x00000001) != 0)) {
output.writeInt64(1, id_); //Field 1
}
if (((bitField0_ & 0x00000002) != 0)) {
output.writeString(2, name_); // Field 2
}
}
The serializer already knows:
id -> field 1 -> int64
name -> field 2 -> string
As a result, serialization becomes little more than writing bytes into a buffer.
For a User object:
var user = UserOuterClass.User.newBuilder()
.setId(11)
.setName("Chandler")
.build();
byte[] byteArray = user.toByteArray();
//Print bytes
System.out.println(Arrays.toString(byteArray));
the generated serializer can directly produce:
[8, 11, 18, 8, 67, 104, 97, 110, 100, 108, 101, 114]
//Hex equivalent
08 0B 12 08 43 68 61 6E 64 6C 65 72
We can construct this data format from the actual objects like this and vice versa.
08 │ ├─ Field Number = 1 └─ Wire Type = Varint
0B │ └─ Value = 11
12 │ ├─ Field Number = 2 └─ Wire Type = Length Delimited
08 │ └─ Length = 8
43 68 61 6E 64 6C 65 72 │ └─ “Chandler”
This way protobuf constructs it’s data system to efficiently serialize and deserialize.
08 │ ├─ Field Number = 1 └─ Wire Type = Varint
0B │ └─ Value = 11
12 │ ├─ Field Number = 2 └─ Wire Type = Length Delimited
08 │ └─ Length = 8
43 68 61 6E 64 6C 65 72 │ └─ “Chandler”
This way Protobuf serializes and deserializes data as efficiently as possible.
Hope this blog helped your understanding of Protobuf and thanks for reading. Feedback is most welcome 😊
References:
- Protobuf Encoding: https://protobuf.dev/programming-guides/encoding/
- Protobuf Compiler: https://protobuf.dev/installation/
- Protobuf Java library: https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java
메타데이터
- post_id
- d2a0b06478f1
- slug
- a-2-field-protobuf-message-generated-nearly-1000-lines-of-java-why-d2a0b06478f1
- url
- https://medium.com/@mandasukresh/a-2-field-protobuf-message-generated-nearly-1000-lines-of-java-why-d2a0b06478f1
- canonical_url
- https://medium.com/@mandasukresh/a-2-field-protobuf-message-generated-nearly-1000-lines-of-java-why-d2a0b06478f1
- author_url
- https://medium.com/@mandasukresh
- status
- ok
- fetched_at
- 2026-06-17 08:20:12