Node.js vs Java: Native HTTP server performance comparison for hello world case

Mayank C
Tech Tonic

--

The recent articles on the performance comparison series have gotten quite popular in the medium community. Initially, I did a comparison of Node/Deno/Bun’s native server with SpringBoot. I got comments that this was an unfair comparison. SpringBoot is a framework and must be compared with a framework on the other side. Honoring the comments, I did a follow-up comparison of SpringBoot with Node/Deno/Bun running on express/fastify. I still got comments that a comparison of native HTTP servers on both sides would be very useful.

In this, and some follow-up articles, I’m going to compare the native servers provided by Node.js, Deno, Bun, and Go with Java’s native server. This article compares Node’s native server with Java’s native server.

All the recent articles can be seen on my author page.

The setup

The tests are executed on MacBook Pro M1 with 16G of RAM.

The software versions are:

  • Node.js v19.8.1
  • Java 20

The native hello world HTTP server code in both cases is as follows:

Node.js

import http from "node:http";

http.createServer((req, resp) => {
try {
if (req.method !== "GET") {
return resp.writeHead(405).end();
}
if (req.url !== "/") {
return resp.writeHead(404).end();
}
resp.writeHead(200, {
"content-type": "text/plain",
});
resp.end("Hello world");
} catch (e) {
resp.writeHead(500).end();
}
}).listen(3000);

Java

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.util.concurrent.Executors;

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;

public class NativeHelloWorldServer {

public static void main(String[] args) throws Exception {
HttpServer server = HttpServer.create(new InetSocketAddress(3000), 0);
server.createContext("/", new MyHandler());
server.setExecutor(Executors.newCachedThreadPool());
server.start();
}

static class MyHandler implements HttpHandler {
@Override
public void handle(HttpExchange t) throws IOException {
String response = "Hello world!";
t.sendResponseHeaders(200, response.length());
OutputStream os = t.getResponseBody();
os.write(response.getBytes());
os.close();
}
}
}

Execution

Each test is executed for 5M (5 million) requests.

The tests are executed for 25, 100, and 300 concurrent connections.

The load test is carried out using Bombardier HTTP testing tool.

The following are the tables showing results for each concurrency level:

Node’s native HTTP server turns out to be faster than Java’s native server. The difference isn’t too much whether it’s RPS or latency numbers, but there is a difference. Also, Node uses very less system resources like CPU and memory.

WINNER: Node.js

There might an argument about the usage of cached thread pool in Java. The other option is to allocate a fixed-size thread pool at the start. Here is a quick test using 100 concurrent connections with a fixed-size thread pool of 125 threads. The only line change in Java code is:

//server.setExecutor(Executors.newCachedThreadPool());
server.setExecutor(Executors.newFixedThreadPool(125));

The results with fixed-size thread pool are as follows:

I don’t see any performance improvements with a fixed-size thread pool.

In the following articles, the comparison with Java will be presented for Deno, Bun, and Go.

UPDATE:

  • The comparison of Java with Deno is now available here
  • The comparison of Java with Bun is now available here
  • The comparison of Java with Go is now available here

Thanks for reading. All comments, suggestions are welcome (especially on the Java part).

--

--