Java Networking with Netty
Transport Layer
At its core, TCP (Transmission Control Protocol) is a reliable, ordered stream of bytes. Thinking about a TCP connection is easy if you imagine it as two pipes: one from the client to the server and one from the server back to the client. Each pipe just carries bytes.
TCP does NOT know about request or response boundaries. It delivers bytes in order, and it takes care of retransmissions, acknowledgements, and flow control so you do not have to. Following our pipe analogy, when you read from a TCP connection, imagine placing a bucket under the pipe; bytes drip in and you accumulate them until you have enough to parse a complete message. That "enough" part is defined by the protocol on top of TCP, not by TCP itself.
Key takeaway: TPC is not a constant stream of raw bytes. The request and response boundaries are defined by the application layer, not TCP itself.
Netty and TCP
Netty is a Java library that gives you a higher-level API around TCP without making you manage threads, selectors, or connection lifecycles by hand.
At the transport layer abstraction level, Netty handles:
-
Connection management: handling opens, closes, pooling, and failures.
-
Non-blocking I/O and event loops: letting you serve many connections without one thread per connection.
-
Pipelines and handlers: letting you register callbacks for events such as data received or connection closed.
With Netty, you think in terms of protocols and messages and callbacks instead of byte buffers and sockets.
Application Layer
At the application layer is where we define the boundaries, i.e. the framing. HTTP is a good example of this. Below we see how different versions of HTTP works on top of TCP.
HTTP Versions
HTTP/1.0 (default)
End of request/response: TCP connection close
With HTTP/1.0, the end of a message is signaled by closing the connection. This works but is expensive: one connection per request/response.
HTTP/1.0 + keep-alive
End of request/response: Content-Length header
This adds persistent connections so you can reuse TCP for multiple requests. The content-length header tells the receiver how many bytes belong to each message. Note that not every client or server supported this extension, and chunked encoding was still not available.
HTTP/1.1 (persistent by default)
When a sender does not know the full body size in advance, it can send chunks. The receiver knows the message ends when it sees a chunk with size 0.
HTTP/1.1 with Connection: close
HTTP/2 (single TCP, multiplexed, interleaved responses)
In HTTP/2, each stream has a numeric stream ID. The rule is simple:
-
Client-initiated streams have odd IDs: 1, 3, 5, …
-
Server-initiated streams have even IDs: 2, 4, 6, …
Stream IDs help the connection track which frames belong to which logical stream. Multiplexing lets multiple streams share a single TCP connection. Interleaving allows faster responses to arrive before slower ones, removing HTTP-level head-of-line blocking. The END_STREAM flag signals when a request or response is complete.
Netty and HTTP
At the application layer, Netty provides protocol-level building blocks. It includes decoders and encoders for HTTP, so you can think in terms of “received HTTP request” or “send HTTP response” instead of “parse bytes from a buffer.” In other words, instead of registering callbacks for raw bytes, you can register them at a higher level of abstraction, for example when an HTTP request is received. This way, Netty handles the details of HTTP framing for you. You can also send responses using the HTTP abstraction, without having to write raw bytes yourself.
The following shows an example HTTP server with Netty:
Note that with a ChannelPipeline, we register a chain of handlers that are called in order for inbound events. HttpServerCodec does the HTTP decoding. HttpObjectAggregator does the aggregation, since HTTP messages can arrive in fragments. For example, headers arrive first as an HttpRequest, and the body may follow in one or more HttpContent objects. For large requests or chunked transfers, the message is split into multiple pieces. Without aggregation, your handler would need to process each fragment individually and manually assemble the full message.
Netty and gRPC
gRPC is an RPC framework that runs on top of HTTP/2. It uses HTTP/2 streams to carry messages encoded with Protocol Buffers (protobuf), and it supports streaming in both directions.
In Java, gRPC uses Netty as the HTTP/2 transport. Netty handles:
-
TCP connections
-
HTTP/2 framing and streams
-
Non-blocking I/O
On top of that, gRPC adds:
-
RPC semantics (methods, services)
-
Message serialization (protobuf)
-
Streaming abstractions
Together, they provide efficient, multiplexed, binary communication for client-server systems.
Multiplexing
Sending Large Files to a Netty Server
Netty makes it easy to handle large file uploads efficiently, even if the
client isn’t using Netty. Let’s say the client is using
curl or any standard HTTP
library to upload a big file.
At the client side, the file is
streamed over HTTP, either
with a Content-Length header
if the size is known, or using
chunked transfer encoding if
it isn’t (see HTTP 1.1 above). The client library reads the file
incrementally and sends bytes over TCP, so you never have to load the
entire file into memory. For example, with
curl:
On the server side, Netty receives the request over the same TCP connection. Its HttpObjectDecoder breaks the incoming bytes into three parts:
-
HttpRequest – the headers and metadata.
-
HttpContent chunks – each chunk of the file body as it arrives.
-
LastHttpContent – marks the end of the file.
Your Netty handler can then
process each chunk as it arrives, writing directly to disk or another sink. This way, only a small chunk
is in memory at any time, making it possible to handle files of gigabytes
in size. Once
LastHttpContent is
received, the server knows the upload is complete and can send a response
back to the client.
Here’s a simplified illustration of what’s happening under the hood:
Key points:
-
The client doesn’t need Netty; any HTTP client works.
-
The server handles large files without loading the entire file into memory.
-
Netty’s pipeline and chunked handling make uploads efficient and backpressure-aware.
This approach scales easily to gigabytes of data and integrates seamlessly with your existing Netty HTTP server.
The following code shows an example:
Conclusion
TCP provides a reliable byte stream, but no message boundaries. Netty abstracts TCP with asynchronous I/O and pipelines so you can focus on protocols. HTTP (1.x and HTTP/2) defines how to frame messages and where request/response ends. gRPC uses HTTP/2 streams and protobuf to give you a high-level RPC mechanism.
Understanding where each piece fits makes it easier to build networked systems that are both correct and performant, and to pick the right abstractions for your code.

Comments
Post a Comment