
August 23, 2026
What Exactly Gets Generated From a `.proto` File in gRPC?
When we first start learning gRPC, one question comes very quickly.
We write a .proto file, run the build, and suddenly multiple Java classes get generated.
But what exactly are those classes?
What does the client use?
What does the server use?
And what do we still need to write ourselves?
In this article, let us understand this using a very small example.
Our Simple user.proto
Let us start with the following .proto file:
syntax = "proto3";
option java_multiple_files = true;
option java_package = "com.example.user";
service UserService {
rpc GetUser(GetUserRequest) returns (GetUserResponse);
}
message GetUserRequest {
int64 user_id = 1;
}
message GetUserResponse {
int64 user_id = 1;
string name = 2;
string email = 3;
}
We have defined three main things here:
UserService— our gRPC serviceGetUserRequest— request sent by the clientGetUserResponse— response returned by the server
Before compiling this file, let us quickly understand two things which might look confusing.
What Are int64, string, and the Numbers 1, 2, 3?
Protocol Buffers has its own data types.
For example:
int64 user_id = 1;
string name = 2;
Here:
int64is the type ofuser_idstringis the type ofname
When Java code is generated, these are mapped to Java types.
For example:
int64 -> long
string -> String
Now what about the numbers?
int64 user_id = 1;
string name = 2;
string email = 3;
The numbers 1, 2, and 3 are called field numbers.
They are not default values, and they are not array indexes.
Protocol Buffers uses these numbers to identify fields while encoding and decoding messages.
For example:
string name = 2;
means:
string→ data typename→ field name2→ unique field number
Once a field number is being used, we should avoid changing or reusing it because older clients or servers may still depend on it.
We will not go deeper into Protocol Buffer encoding in this article. For now, this understanding is enough.
What Happens When We Compile the .proto File?
This is where things become interesting.
There are actually two related kinds of code generation happening.
user.proto
|
+-------------------------+
| |
v v
Protocol Buffer gRPC Java
code generation code generation
| |
v v
Message classes Service classes
The Protocol Buffer compiler generates Java classes for our messages.
For example:
GetUserRequest
GetUserResponse
The gRPC Java plugin generates the service-related code.
For example:
UserServiceGrpc
Inside UserServiceGrpc, we get the code needed by both the client and the server.
Maven Configuration
For a Maven project, we can configure Protocol Buffer and gRPC code generation using the protobuf-maven-plugin.
The important part looks like this:
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>${protobuf.plugin.version}</version>
<configuration>
<protocArtifact>
com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}
</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>
io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}
</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
You do not need to understand every line of this configuration right now.
The important thing is:
compilegenerates the Protocol Buffer message classescompile-customruns the gRPC Java generator and generates the service-related classes
Now we can simply run:
mvn compile
After compilation, Maven generates the Java code from our .proto file.
Where Is the Generated Code?
After running:
mvn compile
the generated code appears under:
target/generated-sources/protobuf/
For our example, the structure looks like this:
target/generated-sources/protobuf/
├── grpc-java/
│ └── com/example/user/
│ └── UserServiceGrpc.java
│
└── java/
└── com/example/user/
├── GetUserRequest.java
├── GetUserRequestOrBuilder.java
├── GetUserResponse.java
├── GetUserResponseOrBuilder.java
└── User.java
So from one small .proto file, multiple Java files were generated.
We can divide them into two groups.
Protocol Buffer generated code
GetUserRequest.java
GetUserRequestOrBuilder.java
GetUserResponse.java
GetUserResponseOrBuilder.java
User.java
gRPC generated code
UserServiceGrpc.java
Now let us understand what these classes actually do.
Generated Message Classes
We defined this message in our .proto file:
message GetUserRequest {
int64 user_id = 1;
}
Protocol Buffers generates a real Java class called:
GetUserRequest
Because of that, we can write:
GetUserRequest request = GetUserRequest.newBuilder()
.setUserId(101L)
.build();
And later read the value using:
long userId = request.getUserId();
Similarly, for:
message GetUserResponse {
int64 user_id = 1;
string name = 2;
string email = 3;
}
we get:
GetUserResponse
and can write:
GetUserResponse response = GetUserResponse.newBuilder()
.setUserId(101L)
.setName("Ritesh")
.setEmail("ritesh@example.com")
.build();
So we do not need to manually create Java POJOs for these request and response objects.
The generated classes already provide:
- fields
- getters
- builders
- parsing
- serialization
- deserialization
That is one major thing generated from the .proto file.
What Are the OrBuilder Classes?
You may also notice these generated files:
GetUserRequestOrBuilder.java
GetUserResponseOrBuilder.java
These are helper interfaces generated by Protocol Buffers.
They expose read-only access methods that are shared by both the generated message class and its builder.
Most application code does not need to use these interfaces directly.
So for now, just remember that they are supporting classes generated by Protocol Buffers.
What Is User.java?
There is one more generated class:
User.java
This may look confusing because we never defined a User message.
This class represents metadata for the .proto file itself.
It contains descriptors describing the messages and services defined inside user.proto.
Most application code will never use this class directly.
So again, it is useful to know why it exists, but it is not something we normally interact with.
Now Comes the Important Part: UserServiceGrpc
So far we have looked at classes generated from the message definitions.
But our .proto file also contains this:
service UserService {
rpc GetUser(GetUserRequest) returns (GetUserResponse);
}
From this service definition, gRPC generates:
UserServiceGrpc
This class contains most of the generated gRPC plumbing required by both the client and the server.
A simplified mental model looks like this:
UserServiceGrpc
|
+---- UserServiceImplBase -> Server
|
+---- UserServiceBlockingStub -> Client
|
+---- UserServiceStub -> Client
|
+---- UserServiceFutureStub -> Client
This is probably the most important generated class to understand.
Let us look at the server side first.
What Gets Generated for the Server?
Inside UserServiceGrpc, gRPC generates a server base class:
UserServiceGrpc.UserServiceImplBase
A simplified version looks like this:
public abstract static class UserServiceImplBase
implements io.grpc.BindableService {
...
}
This gives the server the structure required to expose the UserService.
But gRPC does not know our business logic.
For example, it does not know:
- where users are stored
- whether we use a database
- what validations we perform
- what response should be returned
We need to implement that ourselves.
So our server implementation can look like this:
public class UserServiceImpl
extends UserServiceGrpc.UserServiceImplBase {
@Override
public void getUser(
GetUserRequest request,
StreamObserver<GetUserResponse> responseObserver) {
GetUserResponse response = GetUserResponse.newBuilder()
.setUserId(request.getUserId())
.setName("Ritesh")
.setEmail("ritesh@example.com")
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
}
The important distinction is:
UserServiceImplBase -> Generated by gRPC
|
| extends
v
UserServiceImpl -> Written by us
|
v
Business logic / Database -> Written by us
gRPC gives us the server skeleton.
We implement the actual behavior.
What Gets Generated for the Client?
Now let us look at the client.
Suppose UserService is running on another server.
The client somehow needs to call:
GetUser
Without generated code, the client would have to worry about things such as sending the request over the network, serializing the request, receiving the response, and converting it back into an object.
gRPC hides most of this behind something called a stub.
A stub is the client-side representation of the remote service.
Because of the generated stub, the client can write something like:
GetUserResponse response = stub.getUser(request);
It looks almost like a normal Java method call.
But the actual getUser() implementation is running on the remote gRPC server.
For our UserService, gRPC generates different kinds of stubs.
Blocking Stub
The generated blocking stub is:
UserServiceGrpc.UserServiceBlockingStub
We create it like this:
UserServiceGrpc.UserServiceBlockingStub stub =
UserServiceGrpc.newBlockingStub(channel);
Then call our RPC:
GetUserRequest request = GetUserRequest.newBuilder()
.setUserId(101L)
.build();
GetUserResponse response = stub.getUser(request);
With a blocking stub, the current thread waits until the server returns the response.
This is generally the easiest stub to understand when starting with gRPC.
Async Stub
gRPC also generates:
UserServiceGrpc.UserServiceStub
This is the asynchronous stub.
Instead of waiting for the response directly, we provide a StreamObserver:
asyncStub.getUser(
request,
new StreamObserver<GetUserResponse>() {
@Override
public void onNext(GetUserResponse response) {
System.out.println(response.getName());
}
@Override
public void onError(Throwable throwable) {
}
@Override
public void onCompleted() {
}
}
);
The response is delivered through the callback instead of being returned directly from the method.
Future Stub
The third generated client stub is:
UserServiceGrpc.UserServiceFutureStub
It returns a ListenableFuture:
ListenableFuture<GetUserResponse> future =
futureStub.getUser(request);
This lets us work with the response in a future-based programming style.
So the simple mental model is:
| Stub | Behavior |
|---|---|
UserServiceBlockingStub | Waits and returns the response directly |
UserServiceStub | Returns the response asynchronously through StreamObserver |
UserServiceFutureStub | Returns a ListenableFuture |
For now, you do not need to memorize when to use every stub.
The important thing is to understand why they exist and that they are generated for the client from the same service definition.
Does the Client and Server Use the Same .proto File?
Yes.
This is an important idea in gRPC.
The .proto file acts as the contract between the client and the server.
Both sides understand the same API:
service UserService {
rpc GetUser(GetUserRequest) returns (GetUserResponse);
}
From the same contract:
- the server gets a base class to implement
- the client gets stubs to call
- both sides get the request and response message definitions
So the client and server do not separately create their own request classes or API method definitions.
They derive them from the same .proto contract.
Generated Code vs Code We Write
Now we can summarize everything.
Generated from the .proto
| Proto definition | Generated code | Used by |
|---|---|---|
message GetUserRequest | GetUserRequest | Client + Server |
message GetUserResponse | GetUserResponse | Client + Server |
service UserService | UserServiceGrpc | Client + Server |
rpc GetUser(...) | Server method/base class | Server |
rpc GetUser(...) | Blocking/Async/Future stub methods | Client |
Supporting classes such as OrBuilder interfaces and the User.java descriptor holder are also generated.
Written by us
| Code | Why we write it |
|---|---|
UserServiceImpl | Actual server-side business logic |
| Client application | Builds requests and calls generated stubs |
| Channel configuration | Tells the client how to connect to the server |
| Server configuration | Starts and exposes the gRPC service |
| Database/business logic | Application-specific behavior |
This separation is the main idea to remember.
Complete Mental Model
Here is the full picture:
flowchart TD
A["user.proto"]
A --> B["Protocol Buffer Code Generation"]
A --> C["gRPC Java Code Generation"]
B --> D["GetUserRequest"]
B --> E["GetUserResponse"]
B --> F["Supporting / Descriptor Classes"]
C --> G["UserServiceGrpc"]
G --> H["UserServiceImplBase"]
G --> I["Blocking Stub"]
G --> J["Async Stub"]
G --> K["Future Stub"]
H --> L["Server Implementation<br/>Written by us"]
I --> M["Client Application<br/>Written by us"]
J --> M
K --> M
If we simplify it even more:
user.proto
|
+-------------+-------------+
| |
v v
Message Classes UserServiceGrpc
| |
Client + Server +-------+-------+
| |
v v
Server Base Class Client Stubs
| |
v v
Server code Client code
written by us written by us
Summary
So what exactly gets generated from a .proto file in Java gRPC?
Protocol Buffers generates message classes such as:
GetUserRequest
GetUserResponse
gRPC generates service-related code such as:
UserServiceGrpc
UserServiceImplBase
UserServiceBlockingStub
UserServiceStub
UserServiceFutureStub
The server extends the generated base class and implements the actual business logic.
The client uses one of the generated stubs to call the remote service.
So the simplest mental model is:
The .proto file defines both our messages and our service. From the message definitions, Java request/response classes are generated. From the service definition, gRPC Java generates the server base class and client stubs.