Ritesh Panigrahi
What Exactly Gets Generated From a `.proto` File in gRPC?

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 service
  • GetUserRequest — request sent by the client
  • GetUserResponse — 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:

  • int64 is the type of user_id
  • string is the type of name

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 type
  • name → field name
  • 2 → 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:

  • compile generates the Protocol Buffer message classes
  • compile-custom runs 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.

Source Code


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:

StubBehavior
UserServiceBlockingStubWaits and returns the response directly
UserServiceStubReturns the response asynchronously through StreamObserver
UserServiceFutureStubReturns 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 definitionGenerated codeUsed by
message GetUserRequestGetUserRequestClient + Server
message GetUserResponseGetUserResponseClient + Server
service UserServiceUserServiceGrpcClient + Server
rpc GetUser(...)Server method/base classServer
rpc GetUser(...)Blocking/Async/Future stub methodsClient

Supporting classes such as OrBuilder interfaces and the User.java descriptor holder are also generated.

Written by us

CodeWhy we write it
UserServiceImplActual server-side business logic
Client applicationBuilds requests and calls generated stubs
Channel configurationTells the client how to connect to the server
Server configurationStarts and exposes the gRPC service
Database/business logicApplication-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.