Definition
- Stream multiplexing – HTTP/2 carries many concurrent, independent request/response streams over one TCP connection, which gRPC uses for concurrent RPCs without opening many connections.
- Bidirectional streaming – HTTP/2 supports full-duplex data flow in both directions on the same stream, enabling gRPC's server, client, and bidirectional streaming calls.
- Binary framing – HTTP/2's binary frame layer matches gRPC's binary Protocol Buffers and length-prefixed message framing for efficient transport.
- Trailers – gRPC sends the final status code (
grpc-status) in HTTP/2 trailing headers after the response body, which HTTP/1.1 does not support reliably. - HPACK compression – Header compression reduces per-call overhead for gRPC's metadata headers.
Imagine the line between the transfers desk and the accounts desk at National Bank of India. HTTP/1.1 is like a single phone line where you must finish one whole call before starting the next, and you cannot talk and listen continuously at the same time. HTTP/2 is like a modern switchboard on one wire that carries dozens of calls at once, each independent, and lets both sides talk simultaneously. gRPC was designed for busy, streaming, two-way conversations between desks, so it needs the switchboard. On the old single-call line, its streaming and concurrent-calls features simply cannot happen.
Understanding the Concepts
Why does gRPC need multiplexing?
HTTP/2 carries many logical streams over one TCP connection, each with its own stream ID, interleaved as binary frames. This lets a single gRPC channel run many concurrent calls without opening many connections and without one slow call blocking others. HTTP/1.1 handles one request/response at a time per connection, and pipelining suffers head-of-line blocking.
package com.bank.client;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import java.util.concurrent.CompletableFuture;
import java.util.stream.IntStream;
public class MultiplexingDemo {
public void concurrentCalls() {
// ONE channel, ONE TCP connection
ManagedChannel channel = ManagedChannelBuilder
.forAddress("account-service", 9090)
.usePlaintext()
.build();
// 100 concurrent calls over that single connection
// HTTP/2 multiplexing handles them as independent streams
var futures = IntStream.range(0, 100)
.mapToObj(i -> CompletableFuture.runAsync(() -> {
// Each call is an independent HTTP/2 stream
getBalance(channel, "SAV" + i);
}))
.toList();
CompletableFuture.allOf(
futures.toArray(CompletableFuture[]::new)).join();
channel.shutdown();
}
private void getBalance(ManagedChannel channel, String id) {
System.out.println("Balance for " + id);
}
}
Why does gRPC need bidirectional streaming?
gRPC's server streaming, client streaming, and bidirectional streaming need the ability to send a sequence of messages within a single logical call, in both directions. HTTP/2 DATA frames can flow in both directions on the same stream until it is closed. HTTP/1.1 models one request then one response, with no true full-duplex streaming.
package com.bank.grpc;
import io.grpc.stub.StreamObserver;
import java.math.BigDecimal;
public class StatementGrpcService
extends StatementServiceGrpc.StatementServiceImplBase {
// Server streaming: one request, many responses
@Override
public void getStatement(
StatementRequest request,
StreamObserver<Transaction> observer) {
String accountId = request.getAccountId();
// Stream transactions one by one over HTTP/2
for (int i = 0; i < 100; i++) {
observer.onNext(Transaction.newBuilder()
.setId("TXN-" + i)
.setAmount(new BigDecimal("1500.00")
.toPlainString())
.setDescription("EMI Payment")
.build());
}
observer.onCompleted();
// Status sent in trailers AFTER all data
}
}
Why does gRPC need binary framing?
HTTP/2 is binary by design, which matches gRPC's binary Protocol Buffers payloads and its length-prefixed message framing. HTTP/1.1 is text-oriented with textual headers, which is less efficient.
HTTP/2 Binary Frame: ┌──────────────────────────────────────┐ │ Length (3 bytes) │ │ Type: DATA (0x0) │ │ Stream ID: 5 │ │ Payload: [5-byte gRPC header] │ │ [compressed protobuf msg] │ └──────────────────────────────────────┘ HTTP/1.1 Text: POST /com.bank.AccountService/Transfer HTTP/1.1\r\n Content-Type: application/grpc\r\n ... text headers ...
Why does gRPC need trailers?
gRPC communicates the final call status (grpc-status code and message) in HTTP trailing headers sent after the response body. This is essential for streaming because the status is only known after all messages are sent. HTTP/1.1 does not support trailers reliably.
HTTP/2 Response:
HEADERS frame: content-type: application/grpc
DATA frame: [transaction 1]
DATA frame: [transaction 2]
DATA frame: [transaction 3]
HEADERS frame: grpc-status: 0 ← TRAILER
grpc-message: OK ← TRAILER
HTTP/1.1 cannot send headers AFTER the body!
How is HTTP/2 different from HTTP/1.1 for gRPC? (Comparison)
| Feature | HTTP/2 | HTTP/1.1 |
|---|---|---|
| Multiplexing | Many streams over one connection | One request/response per connection |
| Head-of-line blocking | No (at HTTP layer) | Yes (slow response blocks others) |
| Streaming | Full-duplex bidirectional | Request then response only |
| Framing | Binary frames | Text-based |
| Header compression | HPACK | None |
| Trailers | Supported | Not reliably |
| Connection reuse | Inherent (one connection) | Connection pooling needed |
What breaks when HTTP/2 is not preserved end to end?
An HTTP/1.1-only proxy or an incorrectly terminating load balancer silently breaks gRPC multiplexing and streaming. You must verify HTTP/2 is preserved on every hop between gRPC peers.
┌──────────┐ HTTP/2 ┌──────────┐ HTTP/1.1! ┌──────────┐ │ Client │───────────────→│ Proxy │────────────────→│ Server │ │ │ multiplexed │ (breaks │ one req at a │ │ │ │ streams │ HTTP/2) │ time │ │ └──────────┘ └──────────┘ └──────────┘ Result: multiplexing lost, streaming broken, trailers lost
Why can browsers not speak native gRPC?
Browsers do not expose raw HTTP/2 framing to JavaScript. They use the fetch API and XMLHttpRequest, which do not give access to HTTP/2 streams, trailers, or binary framing directly. This forces the use of gRPC-Web with a translating proxy like Envoy.
Browser ──gRPC-Web (HTTP/1.1 or HTTP/2)──→ Envoy Proxy ──native gRPC──→ Server
│
Translates between
gRPC-Web and gRPC
What about HPACK header compression?
HTTP/2's HPACK compression reduces overhead for the many small metadata headers gRPC sends per call (content-type, grpc-encoding, authority, etc.). HTTP/1.1 sends headers as uncompressed text on every request.
What should a senior engineer know?
Always verify HTTP/2 is preserved on every hop between gRPC peers. An older HTTP/1.1-only proxy or an incorrectly terminating load balancer will silently break multiplexing and streaming. The HTTP/2 dependency is also why gRPC-Web exists and why some older infrastructure requires careful configuration to support gRPC.
Visual Overview
┌───────────────────────────────────────────────────┐ │ HTTP/2 Features gRPC Depends On │ ├───────────────────────────────────────────────────┤ │ │ │ ┌──────────────────────────────┐ │ │ │ Stream Multiplexing │ │ │ │ Stream 1: Transfer call │ │ │ │ Stream 3: Balance call │ ONE TCP conn │ │ │ Stream 5: Statement stream │ │ │ │ Stream 7: Fraud check │ │ │ └──────────────────────────────┘ │ │ │ │ ┌──────────────────────────────┐ │ │ │ Bidirectional Streaming │ │ │ │ Client → DATA DATA DATA │ │ │ │ Server → DATA DATA DATA │ same stream │ │ │ Server → TRAILERS (status) │ │ │ └──────────────────────────────┘ │ │ │ │ ┌──────────────────────────────┐ │ │ │ Binary Framing │ │ │ │ [length][type][stream-id] │ │ │ │ [protobuf payload] │ compact │ │ └──────────────────────────────┘ │ │ │ │ HTTP/1.1 lacks ALL of these: │ │ - No multiplexing (one req at a time) │ │ - No bidirectional streaming │ │ - Text-based headers (no HPACK) │ │ - No reliable trailers │ └───────────────────────────────────────────────────┘
Key Takeaway
gRPC relies on HTTP/2 because its core features map directly onto HTTP/2 capabilities that HTTP/1.1 does not have. HTTP/2 gives stream multiplexing, so many independent RPCs run concurrently over one connection without blocking each other. It gives full-duplex bidirectional streaming, so a single logical call can carry a sequence of messages in both directions. It is binary by design, matching gRPC's Protocol Buffers and length-prefixed framing. And it supports trailers, which gRPC uses to send the final status code after a streamed body.
On HTTP/1.1, several things break. There is no real multiplexing, so a connection handles one request and response at a time, defeating gRPC's concurrent-calls model. There is no true full-duplex bidirectional streaming, so client-streaming and bidirectional calls cannot be represented. There are no reliable trailers, so the status-after-stream mechanism fails. And the text-oriented framing is mismatched with gRPC's binary payloads.
In banking work at National Bank of India a server-streaming statement feed used one request for Rajesh's account to yield a continuous stream of transaction entries on a single call, with the final status delivered in trailers. That pattern only exists because of HTTP/2 streaming and trailers. At the same time the transfer service fires many concurrent account calls over one multiplexed channel without them blocking each other.
The HTTP/2 dependency is exactly why browsers cannot speak native gRPC, since they do not expose raw HTTP/2 framing to JavaScript, so gRPC-Web with a translating proxy like Envoy is required. It is also why HTTP/2 must be verified on every hop, because an older HTTP/1.1-only proxy or an incorrectly terminating load balancer silently breaks multiplexing and streaming.
Remember one sentence: gRPC needs HTTP/2's multiplexing, bidirectional streaming, binary framing, and trailers, and HTTP/1.1 lacks every one of them, so gRPC's concurrency and streaming model cannot run on it.
---