How to Integrate Netty with Spring MVC and Build a Simple HTTP Server

This guide explains the fundamentals of Netty, shows how to combine it with Spring MVC using DispatcherServlet, and provides complete Java code examples for a Netty server, channel pipeline, request handler, server startup, and a sample controller, along with practical configuration tips.

Java High-Performance Architecture
Java High-Performance Architecture
Java High-Performance Architecture
How to Integrate Netty with Spring MVC and Build a Simple HTTP Server

First, understand Netty: a client establishes a long TCP connection with the server, and both client and server process data through a ChannelPipeline using the addLast order, similar to the filter chain in Struts.

Integrating Netty with Spring MVC

After the client‑server connection is created, a class added to the pipeline forwards the request to DispatcherServlet for processing, then sends the result back to the client via ChannelHandlerContext or Channel using writeAndFlush.

1. Netty server implementation

package com.magic.netty.server;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.DispatcherServlet;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import com.magic.netty.HttpServerInitializer;

public class NettyHttpServer {
    private int port;
    private static Logger log = LoggerFactory.getLogger(NettyHttpServer.class);
    private DispatcherServlet servlet;

    public NettyHttpServer(Integer port) { this.port = port; }
    public NettyHttpServer(Integer port, DispatcherServlet servlet) { this.port = port; this.servlet = servlet; }

    public void start() {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .childHandler(new HttpServerInitializer(servlet))
             .option(ChannelOption.SO_BACKLOG, 128)
             .childOption(ChannelOption.SO_KEEPALIVE, true);

            System.out.println("NettyHttpServer Run successfully");
            ChannelFuture f = b.bind(port).sync();
            f.channel().closeFuture().sync();
        } catch (Exception e) {
            log.error("NettyServer start fail", e);
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
}

2. Initialize Netty channel pipeline

package com.magic.netty;

import org.springframework.web.servlet.DispatcherServlet;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpContentCompressor;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.stream.ChunkedWriteHandler;

public class HttpServerInitializer extends ChannelInitializer<SocketChannel> {
    private DispatcherServlet servlet;

    public HttpServerInitializer(DispatcherServlet servlet) { this.servlet = servlet; }
    public HttpServerInitializer() {}

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast("decoder", new HttpRequestDecoder());
        pipeline.addLast("encoder", new HttpResponseEncoder());
        pipeline.addLast("aggregator", new HttpObjectAggregator(2147483647));
        pipeline.addLast("chunkedWriter", new ChunkedWriteHandler());
        pipeline.addLast("deflater", new HttpContentCompressor());
        pipeline.addLast("handler", new HttpRequestHandler(servlet));
    }
}

3. Handler that processes client requests

package com.magic.netty;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.*;
import javax.servlet.ServletContext;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.multipart.*;
import io.netty.util.CharsetUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.util.*;

public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
    private final DispatcherServlet servlet;
    private final ServletContext servletContext;
    private static final Logger logger = LoggerFactory.getLogger(HttpRequestHandler.class);

    public HttpRequestHandler(DispatcherServlet servlet) {
        this.servlet = servlet;
        this.servletContext = servlet.getServletConfig().getServletContext();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) throws Exception {
        logger.error(e.getMessage(), e);
        ctx.close();
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
        boolean isHttp = HttpMethod.POST.equals(request.method()) || HttpMethod.GET.equals(request.method());
        Map<String, String> paramMap = getRequestParams(ctx, request);
        if (isHttp && ctx.channel().isActive()) {
            MockHttpServletResponse servletResponse = new MockHttpServletResponse();
            MockHttpServletRequest servletRequest = new MockHttpServletRequest(servletContext);
            // copy headers
            for (String name : request.headers().names()) {
                for (String value : request.headers().getAll(name)) {
                    servletRequest.addHeader(name, value);
                }
            }
            // URI handling
            String uri = new String(request.getUri().getBytes("ISO8859-1"), "UTF-8");
            uri = URLDecoder.decode(uri, "UTF-8");
            UriComponents uriComponents = UriComponentsBuilder.fromUriString(uri).build();
            String path = URLDecoder.decode(uriComponents.getPath(), "UTF-8");
            servletRequest.setRequestURI(path);
            servletRequest.setServletPath(path);
            servletRequest.setMethod(request.method().name());
            if (uriComponents.getScheme() != null) servletRequest.setScheme(uriComponents.getScheme());
            if (uriComponents.getHost() != null) servletRequest.setServerName(uriComponents.getHost());
            if (uriComponents.getPort() != -1) servletRequest.setServerPort(uriComponents.getPort());
            // body
            ByteBuf content = request.content();
            byte[] data = new byte[content.readableBytes()];
            content.readBytes(data);
            servletRequest.setContent(data);
            // query string and parameters
            try {
                if (uriComponents.getQuery() != null) {
                    servletRequest.setQueryString(UriUtils.decode(uriComponents.getQuery(), "UTF-8"));
                }
                if (paramMap != null && !paramMap.isEmpty()) {
                    for (String key : paramMap.keySet()) {
                        servletRequest.addParameter(
                            UriUtils.decode(key, "UTF-8"),
                            UriUtils.decode(paramMap.get(key) == null ? "" : paramMap.get(key), "UTF-8")
                        );
                    }
                }
            } catch (UnsupportedEncodingException ex) {
                ex.printStackTrace();
            }
            // invoke Spring MVC
            servlet.service(servletRequest, servletResponse);
            // build Netty response
            HttpResponseStatus status = HttpResponseStatus.valueOf(servletResponse.getStatus());
            String result = StringUtils.defaultString(servletResponse.getContentAsString());
            FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status,
                Unpooled.copiedBuffer(result, CharsetUtil.UTF_8));
            response.headers().set("Content-Type", "text/json;charset=UTF-8");
            response.headers().set("Access-Control-Allow-Origin", "*");
            response.headers().set("Access-Control-Allow-Headers", "Content-Type,Content-Length, Authorization, Accept,X-Requested-With,X-File-Name");
            response.headers().set("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
            response.headers().set("Content-Length", response.content().readableBytes());
            response.headers().set("Connection", "keep-alive");
            ChannelFuture writeFuture = ctx.writeAndFlush(response);
            writeFuture.addListener(ChannelFutureListener.CLOSE);
        }
    }

    private Map<String, String> getRequestParams(ChannelHandlerContext ctx, HttpRequest req) {
        Map<String, String> requestParams = new HashMap<>();
        if (req.method() == HttpMethod.GET) {
            QueryStringDecoder decoder = new QueryStringDecoder(req.getUri());
            for (Map.Entry<String, List<String>> entry : decoder.parameters().entrySet()) {
                requestParams.put(entry.getKey(), entry.getValue().get(0));
            }
        }
        if (req.method() == HttpMethod.POST) {
            HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(new DefaultHttpDataFactory(false), req);
            for (InterfaceHttpData data : decoder.getBodyHttpDatas()) {
                if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.Attribute) {
                    MemoryAttribute attribute = (MemoryAttribute) data;
                    requestParams.put(attribute.getName(), attribute.getValue());
                }
            }
        }
        return requestParams;
    }
}

4. Start the Netty server with a Spring DispatcherServlet

package com.magic;

import javax.servlet.ServletException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.mock.web.MockServletConfig;
import org.springframework.web.context.support.XmlWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import com.magic.netty.server.NettyHttpServer;

public class MagicWebServer {
    private static Logger logger = LoggerFactory.getLogger(MagicWebServer.class);

    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        int port = 6001;
        DispatcherServlet servlet = getDispatcherServlet(ctx);
        NettyHttpServer server = new NettyHttpServer(port, servlet);
        server.start();
    }

    public static DispatcherServlet getDispatcherServlet(ApplicationContext ctx) {
        XmlWebApplicationContext mvcContext = new XmlWebApplicationContext();
        mvcContext.setConfigLocation("classpath:spring-servlet.xml");
        mvcContext.setParent(ctx);
        MockServletConfig servletConfig = new MockServletConfig(mvcContext.getServletContext(), "dispatcherServlet");
        DispatcherServlet dispatcherServlet = new DispatcherServlet(mvcContext);
        try {
            dispatcherServlet.init(servletConfig);
        } catch (ServletException e) {
            e.printStackTrace();
        }
        return dispatcherServlet;
    }
}

5. Sample Spring MVC controller and testing URL

package com.magic.controller;

import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.alibaba.fastjson.JSONObject;

@Controller
@RequestMapping(value="/user", produces="text/json;charset=utf-8")
public class UserController {
    @RequestMapping("/login")
    @ResponseBody
    public String login(String username, String pwd) {
        JSONObject resultJson = new JSONObject();
        Map<String, String> loginResult = new HashMap<>();
        loginResult.put("username", username);
        loginResult.put("age", "20");
        loginResult.put("sex", "boy");
        resultJson.put("code", 200);
        resultJson.put("msg", "登录成功");
        resultJson.put("result", loginResult);
        return JSONObject.toJSONString(resultJson);
    }
}

Notes: Ensure that applicationContext.xml and spring-servlet.xml are configured according to standard Spring MVC conventions. If the client receives garbled Chinese characters, add produces = "text/json;charset=utf-8" to the @RequestMapping.

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.

BackendJavaIntegrationNettyHTTP serverSpring MVCDispatcherServlet
Java High-Performance Architecture
Written by

Java High-Performance Architecture

Sharing Java development articles and resources, including SSM architecture and the Spring ecosystem (Spring Boot, Spring Cloud, MyBatis, Dubbo, Docker), Zookeeper, Redis, architecture design, microservices, message queues, Git, etc.

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.