HTTP/2 and Protobuf Are Pushing gRPC Past REST for Internal Services
Foreword
Some colleagues may have encountered this situation at work: after the system goes live, traffic increases, service-to-service calls become more frequent, and response times start to fluctuate unpredictably.
You open the monitoring dashboard and see that the business logic hasn't changed, and database queries have been indexed, but the interface is getting slower and slower.
This isn't because the code is poorly written; it's because the "road" for inter-service communication is too narrow.
A while ago, a colleague reported that their architecture is a typical Spring Cloud microservices setup, with all inter-service calls using REST API + JSON.
During stress testing, when QPS reached around 800, large-scale timeouts began to occur. The CPU couldn't handle it first—not because of business code issues, but because the overhead of serialization and HTTP connections maxed out the CPU.
Later, they conducted an experiment: they changed two high-frequency services in the core chain to use gRPC communication.
With the same business logic and the same machine configuration, QPS nearly tripled, and response time dropped to one-third of the original.
In fact, Spring Boot 4.1.0 already provides official support for gRPC.
gRPC is moving from being a "niche framework" to becoming the "default choice" for more and more companies.
Today, let's talk specifically about gRPC. I hope it will be helpful to you.
For more project practices, visit Java Assault Team Network: susan.net.cn/project
2. Where Exactly is REST Slow?
Before discussing gRPC, let's clarify one question first—where exactly is REST API + JSON slow?
First, the "one request, one connection" limitation of HTTP/1.1.
REST APIs typically run on HTTP/1.1.
Each request requires establishing a new TCP connection, undergoing a three-way handshake.
After the request is processed, the connection is either closed or kept alive with limited Keep-Alive.
Under high concurrency, the overhead of frequently establishing and destroying connections is very large.
Second, JSON is a text protocol, which is large and slow.
JSON is a human-readable text format where each field name must be transmitted repeatedly.
If an object has 10 fields, each request must transmit those 10 field names again.
As the data volume increases, network transmission becomes a bottleneck.
Third, serialization/deserialization overhead is high.
JSON parsing is text parsing, which involves splitting strings into tokens, building an object tree, and then mapping it to a Java object. This process is much slower than binary parsing.
Fourth, there is no connection multiplexing.
Each request is independent and cannot handle multiple requests simultaneously on a single connection.
These issues are almost imperceptible at low concurrency, but once traffic increases, each problem is magnified infinitely.
3. Why is gRPC Fast?
gRPC's core advantages stem from two key underlying technologies.
3.1 HTTP/2: Connection Multiplexing, Eliminating Handshake Overhead
gRPC runs on HTTP/2, which is fundamentally different from HTTP/1.1:
| Feature | HTTP/1.1 | HTTP/2 |
|---|---|---|
| Connection Model | One connection per request | Multiplexing over one connection |
| Connection Establishment | 3-way handshake per request | 1 handshake per session |
| Data Transfer | Text | Binary frames |
| Concurrent Requests | Serial/limited parallel | True parallelism |
| Server Push | ❌ | ✅ |
The core capability of HTTP/2 is multiplexing—handling hundreds or thousands of requests simultaneously on a single TCP connection without blocking each other.
3.2 Protobuf: 60% Smaller than JSON, 5x Faster
gRPC uses Protocol Buffers (Protobuf) as its default serialization protocol.
Protobuf is binary and does not need to convert field names into strings for transmission; it only transmits field numbers and values.
Measured data: Protobuf serialized volume is 60%-80% smaller than JSON, and serialization speed is 3-5 times faster.
The following comparison is very intuitive:
| Metric | REST + JSON | gRPC + Protobuf |
|---|---|---|
| Data Volume | Baseline | 60%-80% reduction |
| Serialization Speed | Baseline | 3-5x improvement |
| Connection Establishment | 3RTT | 1RTT |
| Request Latency | 8-12ms | 2-3ms |
| Throughput | 450 req/s | 1200 req/s |
gRPC's advantage in connection establishment is also significant.
HTTP/1.1 requires a 3-way handshake (3RTT) for each request, whereas gRPC, through HTTP/2 multiplexing, only needs one handshake (1RTT), with subsequent requests using the same connection.
"One handshake, multiple uses"—in high-concurrency scenarios, this gap is magnified infinitely.
4. Understanding gRPC's Overall Architecture in One Diagram
Before diving into the code, let's establish an overall understanding.
From this diagram, you can see that the contract layer (Proto file) is the core of gRPC's architecture.
It defines the service interface and data structures, and then code generation tools produce the skeleton code for the client and server.
Both the client and server generate code based on the same Proto file, ensuring type safety and cross-language consistency.
5. A Complete gRPC Service
Theory alone isn't enough; let's look at the code.
5.1 Step 1: Define the Proto File
syntax = "proto3";
package com.example.grpc;
service UserService {
rpc GetUser (UserRequest) returns (UserResponse);
rpc ListUsers (ListUsersRequest) returns (stream UserResponse);
}
message UserRequest {
int64 id = 1;
}
message ListUsersRequest {
int32 page = 1;
int32 size = 2;
}
message UserResponse {
int64 id = 1;
string name = 2;
string email = 3;
int32 age = 4;
}
5.2 Step 2: Add Dependencies
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.68.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.68.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.68.0</version>
</dependency>
5.3 Step 3: Implement the Server
@GrpcService
public class UserServiceImpl extends UserServiceGrpc.UserServiceImplBase {
@Override
public void getUser(UserRequest request,
StreamObserver<UserResponse> responseObserver) {
// Simulate business logic
long userId = request.getId();
UserResponse response = UserResponse.newBuilder()
.setId(userId)
.setName("User" + userId)
.setEmail("user" + userId + "@example.com")
.setAge(25)
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
}
5.4 Step 4: Configure the Server
grpc:
server:
port: 9090
5.5 Step 5: Client Call
public class UserClient {
public static void main(String[] args) {
ManagedChannel channel = ManagedChannelBuilder
.forAddress("localhost", 9090)
.usePlaintext()
.build();
UserServiceGrpc.UserServiceBlockingStub stub =
UserServiceGrpc.newBlockingStub(channel);
UserRequest request = UserRequest.newBuilder()
.setId(1001L)
.build();
UserResponse response = stub.getUser(request);
System.out.println("User info: " + response.getName());
}
}
6. Official gRPC Support in Spring Boot 4.1.0
Some colleagues might say: "I understand the gRPC theory, but how exactly do I use it in Spring Boot?"
Previously, using gRPC in Spring Boot mostly relied on third-party starters, or manually configuring Servers, Channels, interceptors, and exception handling. As projects grew complex, it became a patchwork, making troubleshooting a headache.
Spring Boot 4.1.0 has incorporated gRPC auto-configuration, allowing both the server and client to follow Spring Boot's auto-assembly logic.
You just need to register the gRPC service as a Spring Bean, and Boot can discover it, then mount the service onto the gRPC Server.
Below, I'll use a complete practical case to walk you through running a gRPC service from scratch.
6.1 Create a Project in Spring Initializr
Open Spring Initializr and directly check the gRPC dependency.
Generate the project, unzip it, and open it with your IDE.
6.2 Add Dependencies
Spring Boot 4.1 provides four official gRPC starters, with versions managed uniformly by Spring Boot's BOM:
<!-- Server dependency -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-grpc-server</artifactId>
</dependency>
<!-- Client dependency -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-grpc-client</artifactId>
</dependency>
<!-- Test dependency (Server) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-grpc-server-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Test dependency (Client) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-grpc-client-test</artifactId>
<scope>test</scope>
</dependency>
Dependency Note: The starters in Spring Boot 4.1 are official, not third-party starters.
The spring-grpc project provides the programming model with @GrpcService, @ImportGrpcClients, and GrpcChannelFactory, which will be introduced as transitive dependencies.
You do not need to explicitly declare spring-grpc in your build.
6.3 Define the Proto File
Create hello.proto in the src/main/proto/ directory:
syntax = "proto3";
option java_multiple_files = true;
option java_package = "com.example.demo.proto"; // Change to your own package name
option java_outer_classname = "HelloWorldProto";
service Simple {
rpc SayHello (HelloRequest) returns (HelloReply) {}
rpc StreamHello (HelloRequest) returns (stream HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
Note: Be sure to change java_package to your own project's package name.
6.4 Generate Stub Code
Execute the Maven build:
./mvnw clean package
The generated files are in the following directories:
- Maven:
target/generated-sources/protobuf/grpc-javaandtarget/generated-sources/protobuf/java - Gradle:
build/generated/source/proto/main/grpcandbuild/generated/source/proto/main/java
In IntelliJ IDEA, right-click these two folders → Mark Directory As → Generated Source Root.
6.5 Implement the Server
Create a service implementation class that extends the generated base class and add the @GrpcService annotation:
import io.grpc.stub.StreamObserver;
import org.springframework.grpc.server.service.GrpcService;
import com.example.demo.proto.SimpleGrpc;
import com.example.demo.proto.HelloRequest;
import com.example.demo.proto.HelloReply;
@GrpcService
public class GrpcServerService extends SimpleGrpc.SimpleImplBase {
@Override
public void sayHello(HelloRequest request,
StreamObserver<HelloReply> responseObserver) {
String name = request.getName();
HelloReply reply = HelloReply.newBuilder()
.setMessage("Hello ==> " + name)
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
@Override
public void streamHello(HelloRequest request,
StreamObserver<HelloReply> responseObserver) {
// Stream multiple messages back
for (int i = 0; i < 5; i++) {
HelloReply reply = HelloReply.newBuilder()
.setMessage("Hello " + request.getName() + " (message " + i + ")")
.build();
responseObserver.onNext(reply);
}
responseObserver.onCompleted();
}
}
The role of @GrpcService is similar to @Service or @RestController, which any Spring developer will find familiar.
6.6 Configure the Server
Configure in application.yml:
spring:
grpc:
server:
port: 9090
The default port is 9090, using Netty transport. Reflection is registered by default, so you can test directly with grpcurl using the -plaintext flag.
6.7 Start the Server
Run the Spring Boot main class directly. After startup, the gRPC server will automatically start on port 9090.
Test with grpcurl:
grpcurl -plaintext -d '{"name":"World"}' localhost:9090 Simple/SayHello
Expected return:
{
"message": "Hello ==> World"
}
6.8 Implement the Client
Step 1: Add Client Dependency
Add to the client's pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-grpc-client</artifactId>
</dependency>
Step 2: Add @ImportGrpcClients to the Main Class
import org.springframework.grpc.client.ImportGrpcClients;
@ImportGrpcClients
@SpringBootApplication
public class GrpcClientApplication {
public static void main(String[] args) {
SpringApplication.run(GrpcClientApplication.class, args);
}
}
@ImportGrpcClients triggers the automatic creation of gRPC stubs.
Step 3: Configure the Client
Configure in application.yml:
spring:
grpc:
client:
channel:
order-service:
target: static://localhost:9090
For production environments, do not hardcode static://localhost:9090; use service discovery or environment variables:
target: static://${ORDER_GRPC_HOST:localhost}:${ORDER_GRPC_PORT:9090}
Step 4: Inject and Use the Stub
import org.springframework.grpc.client.GrpcClient;
import org.springframework.stereotype.Component;
@Component
public class HelloClient {
@GrpcClient("order-service")
private SimpleGrpc.SimpleBlockingStub simpleStub;
public String sayHello(String name) {
HelloRequest request = HelloRequest.newBuilder()
.setName(name)
.build();
HelloReply response = simpleStub.sayHello(request);
return response.getMessage();
}
}
6.9 Unified Exception Handling (@GrpcAdvice)
Spring Boot 4.1 also introduces the @GrpcAdvice annotation for centralized handling of gRPC exceptions:
import io.grpc.Status;
import org.springframework.grpc.server.exception.GrpcExceptionHandler;
import org.springframework.grpc.server.exception.GrpcExceptionHandlerAdvice;
@GrpcExceptionHandlerAdvice
public class GlobalGrpcExceptionHandler {
@GrpcExceptionHandler(IllegalArgumentException.class)
public Status handleBadRequest(IllegalArgumentException ex) {
return Status.INVALID_ARGUMENT
.withDescription(ex.getMessage())
.asRuntimeException();
}
}
6.10 Complete gRPC Flow in Spring Boot 4.1
6.11 Supplementary Note: Configuration Property Changes
If you were previously using spring-grpc 1.0.x (Spring Boot 4.0), note the configuration property changes when migrating to Spring Boot 4.1:
| spring-grpc 1.0 (Boot 4.0) | Spring Boot 4.1 |
|---|---|
spring.grpc.client.channels.<name>.address |
spring.grpc.client.channel.<name>.target |
spring.grpc.client.channels.<name>.default-deadline |
spring.grpc.client.channel.<name>.default.deadline |
spring.grpc.server.address (host:port combination) |
spring.grpc.server.address (address only) + spring.grpc.server.port |
spring.grpc.server.health.actuator.* |
spring.grpc.server.health.* |
Key Point: The Gradle plugin in Spring Boot 4.1 no longer auto-configures gRPC—it only triggers configuration when the Protobuf plugin is applied. If using Maven, this does not apply.
7. Why Are More People Using gRPC?
7.1 Performance Advantage is the Hard Truth
The most intuitive advantage of gRPC is performance. A 2026 benchmark test showed that, under the same business logic, gRPC's throughput was 107% higher than REST, and latency was reduced by 48%.
One team measured that in a test involving 1000 requests, the average latency for REST was about 250ms, while gRPC was only about 25ms.
In high-concurrency scenarios, gRPC avoids the overhead of frequent connection establishment through HTTP/2 multiplexing, making its throughput advantage very obvious.
Another comparative study on API protocols also confirmed this: gRPC has the lowest latency when handling small message payloads, making it very suitable for internal communication between microservices.
7.2 Streaming Communication, More Than Just Request-Response
gRPC supports four communication modes:
| Mode | Description | Typical Scenario |
|---|---|---|
| Unary | One request, one response | Ordinary RPC calls |
| Server Streaming | Client sends one request, server streams back | Real-time logs, event push |
| Client Streaming | Client streams, server returns once | File upload, batch data submission |
| Bidirectional Streaming | Bidirectional streaming communication | Real-time chat, AI streaming dialogue |
This flexibility makes gRPC suitable not only for traditional request-response scenarios but also very suitable for real-time data interaction. The bidirectional streaming mode, in particular, is especially valuable in scenarios where AI Agents require real-time interaction.
7.3 Cross-Language, One Proto Rules Them All
After defining the interface with Proto, you can use protoc to generate code for languages like Java, Go, Python, C++, Node.js, and C#. For the same service, the client and server can be implemented in different languages, with the underlying communication being completely consistent.
This is especially valuable in teams with multi-language tech stacks.
In many companies' microservice architectures now, different services may be written in different languages—gateways in Java, data processing in Python, performance-sensitive services in Go.
gRPC makes cross-language service calls as simple as same-language calls.
7.4 Strongly Typed Contract, Interface as Documentation
The Proto file itself is a precise interface contract.
No extra documentation is needed, and no manual maintenance of API specifications is required. The interface is exactly what the Proto file looks like.
If any party modifies the Proto, regenerating the code will reveal incompatibilities.
8. gRPC Challenges and Considerations
gRPC is not a silver bullet; it also has its shortcomings.
8.1 Browser Compatibility
Browsers do not natively support gRPC and require a gRPC-Web proxy for forwarding. This adds architectural complexity and makes gRPC less convenient than REST for externally exposed API scenarios.
8.2 Debugging Difficulty
gRPC uses the Protobuf binary format, which cannot be viewed directly in a browser like JSON. Troubleshooting requires specialized tools, such as grpcurl, BloomRPC, etc.
8.3 Learning Curve
Using gRPC requires learning a series of new concepts, such as Proto syntax, code generation, and HTTP/2. For teams accustomed to REST APIs, switching involves a certain learning cost.
8.4 K8s Load Balancing Adaptation
gRPC uses long-lived connections, and the default Service load balancing strategy in Kubernetes can cause connection "stickiness." Additional configuration is needed, such as Headless Service + client-side round-robin strategy, or introducing a Service Mesh (like Linkerd/Istio). In contrast, scaling and load balancing for REST APIs are much simpler.
9. Pros and Cons Comparison
| Comparison Dimension | REST + JSON | gRPC |
|---|---|---|
| Data Format | Text (JSON) | Binary (Protobuf) |
| Transport Protocol | HTTP/1.1 | HTTP/2 |
| Connection Multiplexing | ❌ | ✅ Multiplexing |
| Serialization Size | Baseline | 60%-80% reduction |
| Serialization Speed | Baseline | 3-5x improvement |
| Cross-Language Support | Good | Excellent |
| Streaming Communication | ❌ | ✅ Bidirectional |
| Browser Support | ✅ Native | ⚠️ Requires gRPC-Web |
| Debugging Difficulty | Easy | High |
| Learning Curve | Low | Medium |
| Applicable Scenarios | External APIs | Internal Microservices |
10. Applicable Scenarios
| Scenario | Recommendation | Reason |
|---|---|---|
| Inter-Microservice Communication | ✅✅✅ Highly Recommended | Most obvious performance advantage, good cross-language support |
| Multi-Language Mixed Teams | ✅✅✅ Highly Recommended | One Proto generates code for all languages |
| Streaming Data Interaction | ✅✅✅ Highly Recommended | Native support for Server/Client/Bidirectional Streaming |
| AI Agent Real-Time Communication | ✅✅✅ Highly Recommended | Bidirectional streaming mode suitable for Agent real-time interaction |
| High-Performance Gateway Internal Routing | ✅✅ Recommended | Internal forwarding performance far superior to REST |
| External Public APIs | ⚠️ Needs Evaluation | Poor browser compatibility |
| Simple CRUD Applications | ⚠️ Needs Evaluation | REST is sufficient, over-engineering |
| Direct Frontend Calls | ❌ Not Recommended | Requires gRPC-Web proxy |
For more project practices, visit Java Assault Team Network: susan.net.cn/project
11. Final Words
Returning to the original question: Why are more and more people using gRPC?
The answer is actually not complicated—because it uses HTTP/2 to solve the concurrency bottleneck of HTTP/1.1, and uses Protobuf to solve the transmission and parsing overhead of JSON.
For the same business logic, gRPC can achieve two to three times the throughput of REST, with response times reduced by an order of magnitude.
In a microservices architecture, the frequency of inter-service calls is extremely high, and this gap is further magnified.
Of course, gRPC also has its own shortcomings—browser compatibility, debugging difficulty, learning curve.
It is not here to replace REST, but rather as an alternative to REST in internal inter-service communication scenarios.
My suggestion is: if you are building external APIs, REST is still the better choice.
But if your system is a microservices architecture, services need high-frequency communication, or your team has a multi-language tech stack—gRPC is worth spending an afternoon running through the official examples.
One connection multiplexes all requests, one Proto generates code for all languages. You will find that inter-service communication can be this efficient.