Skip to content

Writing a Client

A gRPC client is built from two objects:

  • A channel — the long-lived connection to a server address (host:port). It manages the underlying HTTP/2 connection(s), reconnection, and load balancing.
  • A stub — a thin typed wrapper created from the channel, exposing one method per rpc. Calling stub.GetUser(req) sends the request over the channel and returns the response.
flowchart LR
  stub["stub.GetUser(req)"] --> channel["channel
(host:port, HTTP/2)"]
  stub2["stub.GetUser(req2)"] --> channel
  channel -->|multiplexed| server["server :50051"]
A channel carries every call; the stub is the typed face

Both stubs use the same channel — that’s the point. The channel is expensive to create and cheap to share; the stub is just a typed handle on top.

Here is a full client that creates a channel, builds a stub, calls GetUser, and handles both the response and a possible error:

conn, err := grpc.NewClient("localhost:50051",
grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatal(err)
}
defer conn.Close() // close the channel when the program ends, not per call
client := userv1.NewUserServiceClient(conn) // stub
user, err := client.GetUser(ctx, &userv1.GetUserRequest{Id: 42})
if err != nil {
st, _ := status.FromError(err) // inspect the gRPC status
log.Fatalf("call failed: %s%s", st.Code(), st.Message())
}
fmt.Println(user.Name)

Notice the error is not an exception about the network — it is a status with a code (NOT_FOUND, DEADLINE_EXCEEDED, UNAVAILABLE) and a message. Clients branch on that code.

Reuse the channel — do not create one per call

Section titled “Reuse the channel — do not create one per call”

The single most common client mistake is creating a fresh channel for every request. A channel sets up HTTP/2 connections, name resolution, and load-balancing state — doing that per call throws away gRPC’s biggest efficiency and can exhaust sockets under load.

Create one channel per target, at startup, and share it across all calls and all stubs. Close it only when the program shuts down.

The generated stub usually offers both styles: a blocking/synchronous call that returns the response (simplest for scripts and request handlers) and an async/future variant that returns immediately and completes later (useful for fan-out or non-blocking servers). Pick per call site; both go over the same channel.

What are the two objects a gRPC client is built from?
What is the most common client mistake?
When a call fails, what does the client actually receive?
When should you close a channel?