gRPC: how a local call becomes a network request
Suppose you run a ride-hailing app with five services: trip, dispatch, location, pricing, and payments. The trip service owns each ride. Dispatch matches riders to nearby drivers. Location tracks driver positions. Pricing quotes the fare. Payments charges the card. When a rider taps Find a ride, the trip service calls dispatch to find a driver. The first version of that call is hand-written JSON. Build a URL, encode JSON, set a content type, parse the response, and repeat that code in every service that calls dispatch. Then the dispatch service renames driver_id to driver_ref. The trip service still compiles. At runtime the field is missing, dispatch checks the field, rejects the request, and the ride fails. gRPC starts from the contract instead of the HTTP call. You define the service and its messages in one file. From that file, a compiler generates the client and server code, and the generated client makes the call look like a local method. The gRPC introduction describes this contract-first workflow.
A call that crosses the network
gRPC is a remote procedure call system. The client invokes a method on a stub, a generated local object that stands in for the remote service. The stub serializes the request, sends it to the server, waits for the reply, and returns the result to the caller.
The method name and the message types come from a service definition. The server implements that definition. The client never hand-writes the URL or the JSON body.
Suppose the trip service calls FindDriver with pickup_zone = "airport". The call site reads like a function call.
The network is still between the two processes. The stub hides how the message is encoded and sent. It does not hide the latency, the failure, or the version of the contract on the other side.
The .proto file is the contract
A gRPC service lives in a .proto file. The file defines messages, which are typed records, and a service, which lists the methods that can be called.
service Dispatch {
rpc FindDriver (RideRequest) returns (DriverReply);
rpc WatchDrivers (AreaRequest) returns (stream DriverLocation);
rpc ReportRoute (stream RoutePoint) returns (RouteSummary);
rpc TrackTrip (stream TripEvent) returns (stream TripUpdate);
}
message RideRequest {
string pickup_zone = 1;
string rider_id = 2;
}
message DriverReply {
string driver_id = 1;
int32 eta_seconds = 2;
}Each message field has a name and a number. The number is the field's identity on the wire, and the binary encoding uses the number, not the name.
Changing the name can break generated source code or a JSON client, but it does not change the field number. Reusing a number for a different field is not safe, which is why deleted numbers should be reserved (protobuf overview).
The service block is the contract the compiler consumes. It says FindDriver takes a RideRequest and returns a DriverReply, and the other three methods stream. It does not say which programming language either side uses.
The compiler writes the plumbing
protoc reads the .proto file. A gRPC plugin adds the client stub and server skeleton to the normal message classes.
The client gets a stub with a FindDriver method. The server gets an interface it must implement. The generated code serializes the request, dispatches the method, and makes the transport call.

This is a build step, not a runtime library you can skip. Change the .proto file and you regenerate both sides. That keeps the contract honest, and it adds a toolchain every contributor must run.
What the stub sends
Suppose the client calls FindDriver with pickup_zone = "airport". The stub produces a request message and encodes it with protobuf, a schema-based binary format. It opens or reuses an HTTP/2 stream, a multiplexed connection that carries several calls at once.
The request carries the method path, the encoded message, and optional metadata, key-value headers attached to the call, such as an authorization token. HTTP/2 multiplexes several calls over one connection. The client does not need a new TCP connection for every request (RFC 9113).
The server decodes the message, dispatches to FindDriver, and sends back an encoded reply with a gRPC status. A non-OK status is not an HTTP 500. It is a gRPC status such as NOT_FOUND or DEADLINE_EXCEEDED. The status travels in the response trailers, the headers sent after the response body (gRPC status codes).
The client can set a deadline. When the deadline passes, the call ends with DEADLINE_EXCEEDED, even if the server is still working. The server can query the remaining time. It can stop work that no client is waiting for (gRPC core concepts).
Four ways to stream
A unary call sends one request and gets one reply. That is the shape most people mean by RPC.
gRPC also defines three streaming shapes:
- Server streaming: one request, then a stream of replies.
WatchDriversuses this shape to send live driver locations. - Client streaming: a stream of requests, then one reply.
ReportRouteuses this shape to send GPS points and receive one route summary. - Bidirectional streaming: both sides send independent streams.
TrackTripuses this shape to exchange trip events and status updates.
The shapes matter because the method signature carries the direction. returns (stream ItemReply) is a server stream. (stream ItemRequest) is a client stream. Both sides are visible in the .proto file before any code is generated.

Streaming fits a live driver feed, a GPS upload, or a trip-status channel. It does not make a slow query faster. The same database work still happens behind the stream.
What the contract buys
First, the client is typed. The compiler catches a wrong argument type before the request leaves the process. A hand-written JSON client catches that at runtime, if it catches it at all. The same .proto file can generate a Go client and a Java server (gRPC introduction).
The second gain is one contract across languages. A Go client and a Java server both generate from the same file. Neither team hand-writes a serializer.
The third gain is streaming without a second protocol. The four call shapes use the same service definition and the same generated code path.
Fourth, a deadline travels with the call. The client sets it, the server sees it, and a cancelled call can stop work instead of finishing for a client that has gone.
None of this makes gRPC automatically faster than JSON over HTTP. The network can dominate a small call. Measure the call path before assuming the binary format is the bottleneck.
What it costs
Every contributor must run a build step for the contract. A new contributor needs protoc, the language plugin, and a generated-code step in CI. A stale generated file can compile while disagreeing with the service, so CI should regenerate it.
When a message is binary, curl shows bytes, not a readable JSON object. You need a decoder, a proxy, or a logging path that understands protobuf.
Browsers cannot call a normal gRPC service directly. You need gRPC-Web and a proxy, or a JSON gateway (gRPC-Web). That is extra infrastructure for a public web client.
HTTP/2 changes connection behavior. A long-lived connection can pin a client to one backend. The balancer then has to balance requests or streams, not only connections. A layer 4 balancer, which balances whole connections, can leave one server busy while another idles (gRPC load balancing).
When you change the schema, take care. Adding a field is usually safe. Renaming a field, changing its type, or reusing a field number can break old clients (protobuf update guide). The compiler checks types, not whether old clients can read the new field.
When JSON is the better fit
Use a JSON API when the client is a browser you do not control. A browser needs gRPC-Web, a browser-compatible variant, plus a proxy. It can also use a gateway that translates between gRPC and JSON (gRPC-Web). Use JSON when the API is public, or when the payload is small and the team values readable requests over generated code.
Use gRPC when both sides are services you build. Use it when the contract is shared across languages, when you need streaming, or when a deadline and typed errors remove repeated plumbing.
You can keep a public JSON edge and call internal gRPC services. The edge translates once. The internal services get the typed contract.
common questions
Is gRPC faster than REST?
Not by definition. It often uses a compact binary format and one multiplexed connection, but latency, payload size, connection reuse, and server work decide the result.
Does gRPC replace HTTP?
No. It uses HTTP/2 as its transport ([RFC 9113](https://www.rfc-editor.org/rfc/rfc9113.html)). The method call is mapped onto an HTTP/2 request and response, with gRPC status in the trailers.
Can a browser call gRPC directly?
Not a normal gRPC service. Browsers need gRPC-Web plus a proxy, or a JSON gateway.
What is protobuf?
A schema language and binary encoding for typed messages. gRPC uses it by default for the service definition and the payload.
Do I need HTTP/2?
gRPC is designed around HTTP/2 features such as multiplexed streams. A proxy or gateway may expose a different protocol at the edge.
How do errors work?
The server returns a gRPC status code and message in the trailers. A deadline or cancellation ends the call even if the server would have succeeded later. If either side ignores the deadline, work continues for a client that has already given up.
leave a note