Why Choose Netty Over Tomcat for High‑Performance HTTP Servers

This article explains the motivations for using Netty's HTTP protocol stack instead of a traditional Tomcat web container, covering high‑concurrency support, lower resource consumption, detailed HTTP request/response handling, custom decoders, memory‑leak prevention, and keep‑alive connection management.

21CTO
21CTO
21CTO
Why Choose Netty Over Tomcat for High‑Performance HTTP Servers

Recently I needed to build a video‑quality statistics system that collects data from PC, mobile, and web clients via a single HTTP request, parses it on the server, and performs real‑time and offline analysis.

Although the task could be handled by a simple Tomcat web project, I chose Netty because it naturally supports 100k+ TPS with its multithreaded, asynchronous, non‑blocking model, avoids the heavyweight Tomcat container, and offers better CPU, memory, and context‑switch usage.

Netty HTTP Basics

Understanding HTTP is essential: a request consists of a request line, headers, and an optional message body; a response includes a status line, headers, and a body. GET parameters are in the URI, while POST data resides in the message body.

Netty Built‑in HTTP Codecs

HttpRequestDecoder : decodes ByteBuf into HttpRequest and HttpContent.

HttpResponseEncoder : encodes HttpResponse or HttpContent into ByteBuf.

HttpServerCodec : combines the above two for easier server‑side implementation.

These codecs must be added to the ChannelPipeline, e.g.:

ch.pipeline().addLast("codec", new HttpServerCodec());

For full POST handling, add HttpObjectAggregator to aggregate HttpMessage and HttpContent into a FullHttpRequest.

GET Request Parsing

Extract the URI and use QueryStringDecoder to split the path and parameters:

HttpRequest request = (HttpRequest) msg;
String uri = request.uri();
QueryStringDecoder decoder = new QueryStringDecoder(uri, Charsets.toCharset(CharEncoding.UTF_8));
Map<String, List<String>> params = decoder.parameters();

POST Request Parsing

Convert the message to FullHttpRequest and handle different Content‑Types:

application/json : read the content bytes, convert to a string, and parse with JSON.parseObject.

application/x-www-form-urlencoded : reuse QueryStringDecoder on the body string.

multipart/form-data : use HttpPostRequestDecoder to obtain FileUpload objects and save them to disk.

FullHttpRequest full = (FullHttpRequest) msg;
String json = full.content().toString(Charsets.toCharset(CharEncoding.UTF_8));
JSONObject obj = JSON.parseObject(json);

Custom Decoders

Implement a decoder by extending MessageToMessageDecoder and overriding decode. Example decoders include HttpJsonDecoder (produces a JSONObject) and HttpProtobufDecoder (produces a protobuf message).

public class HttpJsonDecoder extends MessageToMessageDecoder<HttpRequest> {
    @Override
    protected void decode(ChannelHandlerContext ctx, HttpRequest msg, List<Object> out) throws Exception {
        FullHttpRequest req = (FullHttpRequest) msg;
        String json = req.content().toString(Charsets.toCharset(CharEncoding.UTF_8));
        out.add(JSON.parseObject(json));
    }
}

Common Pitfalls

Memory leaks : Netty uses reference‑counted objects (e.g., ByteBuf). Always release them in channelRead or use ReferenceCountUtil.release(msg). Failure to release leads to warnings like "ByteBuf.release() was not called before it's garbage‑collected".

HTTP keep‑alive : HTTP/1.1 defaults to persistent connections. If the server does not set Content‑Length or use chunked transfer encoding, the client cannot know when the response ends, causing long waits. Add the appropriate header or close the connection explicitly.

private void writeResponse(Channel ch, HttpResponseStatus status, String msg, boolean forceClose) {
    ByteBuf buf = Unpooled.wrappedBuffer(msg.getBytes());
    FullHttpResponse resp = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, buf);
    if (!forceClose) {
        resp.headers().set(HttpHeaderNames.CONTENT_LENGTH, buf.readableBytes());
    }
    ChannelFuture f = ch.writeAndFlush(resp);
    if (forceClose) {
        f.addListener(ChannelFutureListener.CLOSE);
    }
}

Understanding TCP keep‑alive vs. HTTP keep‑alive is also important: TCP keep‑alive probes the connection’s liveness, while HTTP keep‑alive allows multiple requests over the same TCP connection, reducing latency and resource usage.

Conclusion

The article provides a practical walkthrough of building a Netty‑based HTTP server, covering protocol basics, built‑in codecs, request parsing, custom decoders, and troubleshooting common issues such as memory leaks and persistent connections. The accompanying GitHub project ( https://github.com/cyfonly/netty-http ) demonstrates these concepts in a runnable example.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Nettyhigh concurrencyHTTP server
21CTO
Written by

21CTO

21CTO (21CTO.com) offers developers community, training, and services, making it your go‑to learning and service platform.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.