gRPC: a high-performance Remote Procedure Call (RPC) framework, initially developed by Google and later released as an open source project. It was designed specifically to handle fast, platform-independent communication between services, making it crucial for both backend architectures and mobile clients. It lets your services communicate as if they were just calling local functions. Its speed and efficiency come down to three main components:
Protocol Buffers (Protobuf)
This is both the Interface Definition Language (IDL) and the binary message format. Instead of loose JSON payloads, gRPC requires a strict contract defined in .proto files. Because Protobuf messages are compiled into binary, they are 50-85% smaller and 3–10x faster to parse than text-based formats (Source: Zuplo, “Protobuf vs JSON: Choosing the Right API Serialization Format,” May 2026).
HTTP/2 Transport
Unlike REST APIs relying on HTTP/1.1, gRPC takes advantage of HTTP/2. This unlocks advanced network features like multiplexing (sending multiple requests over a single connection), header compression, and bi-directional streaming – all of which reduce latency.
Automated Code Generation
You write the .proto file, and the protoc compiler handles the rest, generating client stubs and server skeletons in almost any language. When a client makes a call, the stub automatically encodes the request into binary and fires it over HTTP/2. The server receives it, decodes it, runs the method, and sends it back. No manual serialization required.
gRPC vs. REST
Choosing between the two frameworks usually comes down to a few fundamental differences in how they handle data and the network
| Feature | REST | gRPC |
|---|---|---|
| Protocol | HTTP/1.1 (usually) | HTTP/2 |
| Payload | JSON (Text-based) | Protobuf (Binary) |
| Contract | Optional (OpenAPI/Swagger) | Strict (.proto files) |
| Streaming | Request-Response (No native streaming) | Unary, Server, Client, & Bi-directional |
Who is Using It?
Major tech companies lean on gRPC for their most critical, high-throughput operations. Some known examples of gRPC usage are:
- Uber: Relies on gRPC for its real-time push platform and its GitFarm service, which provides a unified, high-speed Git checkout system for monorepos (Sources: “Uber’s Next Gen Push Platform on gRPC,” Aug 2022 & “GitFarm: Git as a Service for Large-Scale Monorepos,” July 2026), both of which need high reliability and low latency.
- Netflix: Netflix’s GraphQL DGS layer calls downstream gRPC microservices (like Recommendations and Watch-History) for internal speed, while GraphQL fronts the external API, as illustrated in their architecture below:

gRPC vs. REST
Choosing between the two frameworks usually comes down to a few fundamental differences in how they handle data and the network
| Feature | REST | gRPC |
|---|---|---|
| Protocol | HTTP/1.1 (usually) | HTTP/2 |
| Payload | JSON (Text-based) | Protobuf (Binary) |
| Contract | Optional (OpenAPI/Swagger) | Strict (.proto files) |
| Streaming | Request-Response (No native streaming) | Unary, Server, Client, & Bi-directional |
Who is Using It?
Major tech companies lean on gRPC for their most critical, high-throughput operations. Some known examples of gRPC usage are:
- Uber: Relies on gRPC for its real-time push platform and its GitFarm service, which provides a unified, high-speed Git checkout system for monorepos (Sources: “Uber’s Next Gen Push Platform on gRPC,” Aug 2022 & “GitFarm: Git as a Service for Large-Scale Monorepos,” July 2026), both of which need high reliability and low latency.
- Netflix: Netflix’s GraphQL DGS layer calls downstream gRPC microservices (like Recommendations and Watch-History) for internal speed, while GraphQL fronts the external API, as illustrated in their architecture below:
- Google: Beyond using gRPC to power its own global services, Google Cloud is currently contributing a gRPC transport package for the Model Context Protocol (MCP). This bridges a gap for companies that already run gRPC-heavy infrastructures and want to plug AI agents into their microservices without falling back to JSON-RPC over HTTP.
Practical Example: Product Service
Let’s look at a practical example. Imagine an e-commerce platform where a Node.js frontend needs to fetch inventory data from a Java backend. We’ll define a Product Service that allows a Java (Spring) server to provide product details, while a TypeScript client consumes them with end-to-end type safety.
The Contract (ProductService.proto)
syntax = "proto3";
option java_multiple_files = true;
package com.techbite.inventory;
service ProductService {
// Returns product details based on an ID
rpc GetProductById (ProductRequest) returns (ProductResponse);
rpc SearchProducts (stream ProductRequest) returns (stream ProductResponse);
}
message ProductRequest {
string id = 1;
}
message ProductResponse {
string id = 1;
string name = 2;
int32 price_cents = 3;
int32 quantity = 4;
repeated string categories = 5; // List of strings
}
The Java Server Implementation
Note: The @GrpcService annotation below is typically provided by the grpc-spring-boot-starter dependency (e.g., net.devh.boot.grpc.server.service.GrpcService).
Base classes are not in your repository right away, they are generated when you compile the project. Running a build command such as mvn compile or ./gradlew build triggers the protoc compiler, which generates the ProductServiceGrpc class and its inner ProductServiceImplBase stub. From there, your actual service class just:
- Extends that generated base
- Overrides the contract methods
- Uses a StreamObserver to stream responses back to the client
@GrpcService
public class ProductServiceImpl extends ProductServiceGrpc.ProductServiceImplBase {
@Override
public void getProductById(
ProductRequest request,
StreamObserver<ProductResponse> responseObserver
) {
// Mocking a database lookup
ProductResponse response = ProductResponse.newBuilder()
.setId(request.getId())
.setName("Mechanical Keyboard")
.setPriceCents(99999)
.setQuantity(2)
.addCategories("Components")
.addCategories("Peripherals")
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
@Override
public StreamObserver<ProductRequest> searchProducts(
StreamObserver<ProductResponse> responseObserver
) {
return new StreamObserver<ProductRequest>() {
@Override
public void onNext(ProductRequest request) {
// Process incoming request stream and send back responses
ProductResponse response = ProductResponse.newBuilder()
.setId(request.getId())
.setName("Mechanical Keyboard")
.setPriceCents(99999)
.setQuantity(2)
.addCategories("Components")
.addCategories("Peripherals")
.build();
responseObserver.onNext(response);
}
@Override
public void onError(Throwable t) {}
@Override
public void onCompleted() {
responseObserver.onCompleted();
}
};
}
}
The TypeScript Client
Just like on the server, these TypeScript definitions aren’t committed to your repository out of the box. Instead, you manage them through your frontend build pipeline by running a custom script like npm run compile-protos, which uses a tool called proto-loader-gen-types to compile the .proto file into type stubs. If the API contract ever changes, you simply rerun this script to instantly overwrite and regenerate the definitions, keeping your end-to-end type safety intact.
import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';
import { ProductServiceClient } from './types/com/techbite/inventory/ProductService';
import { ProductRequest } from './types/com/techbite/inventory/ProductRequest';
import { ProductResponse } from './types/com/techbite/inventory/ProductResponse';
const packageDef = protoLoader.loadSync('./proto/product.proto');
const proto = (grpc.loadPackageDefinition(packageDef) as any).com.techbite.inventory;
const client = new proto.ProductService(
'localhost:9090',
grpc.credentials.createInsecure()
) as ProductServiceClient;
const request: ProductRequest = {
id: '7b32a1e4-5f91-4c20-8d3b-92e7411fb2d6'
};
client.getProductById(
request,
(err: grpc.ServiceError | null, product?: ProductResponse) => {
if (err) {
console.error(`RPC Error (${err.code}): ${err.message}`);
return;
}
if (product) {
console.log(`Retrieved: ${product.name} ($${(product.priceCents / 100).toFixed(2)})`);
console.log(
`Stock: ${product.quantity} | Tags: ${product.categories?.join(', ')}`
);
}
}
);
const stream = client.searchProducts();
// 1. Listen for incoming responses from the server
stream.on('data', (response: ProductResponse) => {
console.log(`[Stream] Received: ${response.name} ($${(response.priceCents / 100).toFixed(2)})`);
});
stream.on('error', (err: grpc.ServiceError) => {
console.error(`[Stream] Error: ${err.message}`);
});
stream.on('end', () => {
console.log('[Stream] Server closed the stream.');
});
// 2. Push multiple requests to the server over the same connection
stream.write({ id: '12345-a' });
stream.write({ id: '67890-b' });
// 3. Tell the server we are done sending requests
stream.end();
The Drawbacks
- Browser Support: Browsers do not provide the level of control over HTTP/2 that gRPC requires. To make it work in a web app, you usually have to rely on proxies like gRPC-Web, which adds architectural complexity.
- Not Human-Readable: Unlike JSON, which you can easily inspect in a browser console or Postman, Protobufs are binary. If you don’t have the .proto file to decode the message, you can’t read the payload.
- Steeper Learning Curve: Compared to a REST API, gRPC requires more upfront work. You have to actively manage your .proto files across multiple services and configure the right tools for code generation in your build pipeline.
- Missing Semantic Layer for AI: While gRPC’s server reflection tells you the structure of the data (method names, parameters), it lacks the natural-language descriptions that AI agents and LLMs need to understand why or when to use a service. This creates friction with AI-first protocols like MCP.
So, REST or gRPC?
While the drawbacks are worth considering, the performance and type-safety benefits of gRPC make it an excellent tool. Instead of trying to replace REST entirely, you’ll generally want to reach for gRPC in a few specific scenarios:
- For internal service-to-service communication where high throughput is critical, gRPC is practically unmatched. This is especially true if you’re dealing with polyglot environments, those strict .proto contracts guarantee that your Java backend, Node.js service, and Go microservices are speaking the exact same language without runtime surprises.
- Need real-time, bi-directional streaming? gRPC over HTTP/2 handles this natively (and much more elegantly than WebSockets or long-polling).
But REST and GraphQL aren’t going anywhere. Standard HTTP is also way easier for browser-to-server calls, unless you actually want the headache of maintaining gRPC-Web proxies. Ultimately, if you’re just building a basic CRUD app to push JSON around, gRPC is complete overkill.