gRPC Channels and Stubs
Before a client application calls any gRPC method, it needs two things: a channel to open a connection to the server, and a stub (also called a client) to make the actual calls. These two objects are the gateway to every gRPC interaction from the client side.
What a Channel Is
A channel is a logical connection to a gRPC server. It holds the server address, the security settings, and configuration options. A channel does not open a real TCP connection immediately — it connects lazily, on the first call that needs it.
┌──────────────────────────────────────────────────────────────┐ │ gRPC Channel │ │ │ │ Address: payment.internal:443 │ │ Security: TLS (encrypted) │ │ State: IDLE → CONNECTING → READY → TRANSIENT_FAILURE │ │ │ │ Underneath: manages a pool of HTTP/2 connections │ │ Reuses: existing connections for multiple stubs │ └──────────────────────────────────────────────────────────────┘
Channel States
A channel moves through five possible states during its lifetime. Understanding the states helps you write proper health-check and retry logic.
Channel State Machine:
┌─────┐ first call ┌────────────┐ success ┌───────┐
│ IDLE│────────────────►│ CONNECTING │────────────►│ READY │
└─────┘ └────────────┘ └───────┘
▲ │ │
│ │ fails │ network drops
│ idle timeout ▼ ▼
│ ┌──────────────────┐ ┌──────────────────┐
└──────────────────│ TRANSIENT_FAILURE│ │ TRANSIENT_FAILURE│
└──────────────────┘ └──────────────────┘
│
too many failures
▼
┌──────────────┐
│ SHUTDOWN │
└──────────────┘
• IDLE: channel exists but no active connection
• CONNECTING: handshake in progress
• READY: connected, calls can proceed
• TRANSIENT_FAILURE: temporary error, will retry
• SHUTDOWN: channel closed, no more calls
Creating a Channel
Creating a channel is the first step in any gRPC client application. The examples below show the same concept in three common languages.
Go:
import "google.golang.org/grpc"
import "google.golang.org/grpc/credentials/insecure"
// Insecure channel (development only)
conn, err := grpc.NewClient(
"localhost:50051",
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
defer conn.Close()
// TLS channel (production)
creds, _ := credentials.NewClientTLSFromFile("cert.pem", "")
conn, err := grpc.NewClient("payment.internal:443",
grpc.WithTransportCredentials(creds),
)
Python:
import grpc
# Insecure (development)
channel = grpc.insecure_channel("localhost:50051")
# TLS (production)
with open("cert.pem", "rb") as f:
creds = grpc.ssl_channel_credentials(f.read())
channel = grpc.secure_channel("payment.internal:443", creds)
Java:
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
ManagedChannel channel = ManagedChannelBuilder
.forAddress("localhost", 50051)
.usePlaintext() // insecure, development only
.build();
What a Stub Is
A stub is a generated object that wraps the channel and exposes the service methods as callable functions. You never write networking code — you call stub methods exactly like local functions, and the stub handles everything else.
Client Application Code gRPC Stub (generated) Network
──────────────────────────────────────────────────────────────────────
stub.GetUser(request) ──► serialise request to bytes ──► Server
add headers, open stream │
User response ◄── deserialise response bytes ◄── Server
Stub Types (Blocking vs Async vs Future)
Most gRPC libraries provide multiple stub flavours. The right one depends on whether you want to block the current thread or handle the response later.
Java stub types: ┌─────────────────────┬─────────────────────────────────────────────┐ │ Stub Type │ Behaviour │ ├─────────────────────┼─────────────────────────────────────────────┤ │ BlockingStub │ Blocks the thread until response arrives │ │ AsyncStub │ Returns immediately, uses callback │ │ FutureStub │ Returns a Future you can poll or await │ └─────────────────────┴─────────────────────────────────────────────┘ Go: // Go has one stub type — always uses context for cancellation client := pb.NewUserServiceClient(conn) resp, err := client.GetUser(ctx, req) // blocks goroutine, not thread Python: // Synchronous (blocking) stub = UserServiceStub(channel) response = stub.GetUser(request) // Asynchronous future = stub.GetUser.future(request) response = future.result()
Creating a Stub
The stub constructor always takes the channel as its first argument. One channel can serve many stubs — you do not need a separate connection per service.
Go — one channel, multiple stubs:
conn, _ := grpc.NewClient("service.internal:443", opts...)
userClient := pb.NewUserServiceClient(conn)
orderClient := pb.NewOrderServiceClient(conn)
paymentClient := pb.NewPaymentServiceClient(conn)
// All three stubs share the same underlying connection pool
Python:
channel = grpc.insecure_channel("localhost:50051")
user_stub = user_pb2_grpc.UserServiceStub(channel)
order_stub = order_pb2_grpc.OrderServiceStub(channel)
Java:
ManagedChannel channel = ManagedChannelBuilder
.forAddress("localhost", 50051).usePlaintext().build();
UserServiceGrpc.UserServiceBlockingStub userStub =
UserServiceGrpc.newBlockingStub(channel);
Channel Configuration Options
Channels accept options that control connection behaviour, retries, and message size limits.
Go channel options:
grpc.NewClient(
"server:443",
grpc.WithTransportCredentials(creds),
grpc.WithDefaultCallOptions(
grpc.MaxCallRecvMsgSize(10 * 1024 * 1024), // 10 MB max response
grpc.MaxCallSendMsgSize(10 * 1024 * 1024), // 10 MB max request
),
grpc.WithKeepaliveParams(keepalive.ClientParameters{
Time: 10 * time.Second, // ping server every 10s
Timeout: 5 * time.Second, // wait 5s for ping reply
PermitWithoutStream: true, // ping even with no active calls
}),
)
Channel Pooling Diagram
Application Process ┌─────────────────────────────────────────────────────────────────┐ │ │ │ UserServiceStub ──┐ │ │ OrderServiceStub ──┼──► Channel ──► TCP Connection 1 ──►Server│ │ AuthServiceStub ──┘ └──► TCP Connection 2 ──► Server │ │ │ │ • One channel per server address │ │ • Channel holds multiple TCP connections internally │ │ • Many stubs share one channel safely │ └─────────────────────────────────────────────────────────────────┘
Closing the Channel
Always close the channel when your application shuts down. An unclosed channel leaks TCP connections and goroutines.
Go:
conn, _ := grpc.NewClient(...)
defer conn.Close() ← runs when function returns
Java:
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
Python:
channel.close()
// Or use as context manager:
with grpc.insecure_channel("localhost:50051") as channel:
stub = UserServiceStub(channel)
// channel closes automatically when 'with' block exits
Summary
A channel manages the connection to a gRPC server and goes through five states during its life. A stub uses the channel to expose service methods as regular function calls. One channel can power many stubs simultaneously. Always close the channel on application shutdown. For production, always create a channel with TLS credentials, never with insecure plain-text.
