Introduction

Whenever there is a need to choose a technology for an upcoming project or product, there is always a question about which technology/framework performs best for a certain kind of application. Imagine that we’re trying to build an I/O intensive application such as a URL shortener service. For this kind of I/O intensive application, which of these technologies/frameworks will perform best:

Node.js is interpreted, Go is compiled, while Java is both. If the application under consideration is I/O bound, will there be any performance difference in these diverse technologies? We’ll try to find the answer in this article.

Environment

To save space, all the application code is located in this GitHub repo:

https://github.com/mayankc1/node-vs-go-vs-springboot-performance?embedable=true

A URL shortener application has been chosen for benchmarking, as it represents a real-world use case while remaining simple enough for controlled testing.

The application logic is roughly as follows:

Node.js, by default, runs on a single core. This makes it unfair because Go and SpringBoot, by default, use all cores.

To utilize the CPU to the fullest, Node.js application runs in cluster mode (4 workers). Both Go and SpringBoot utilize all cores by default.

A PostgreSQL database is used for storing shortened URLs. The table structure is as follows:

urlshortener=> \d shortenedurls
                        Table "public.shortenedurls"
    Column    |            Type             | Collation | Nullable | Default 
--------------+-----------------------------+-----------+----------+---------
 id           | character varying(255)      |           | not null | 
 srcurl       | character varying(255)      |           | not null | 
 created      | timestamp without time zone |           |          | 
 lastaccessed | timestamp without time zone |           |          | 
Indexes:
    "firstkey" PRIMARY KEY, btree (id)

To ensure high-quality load generation, the widely recognized Bombardier tool was used. The tool was customized to send a random and unique URL in the request body, formatted as a JSON string.

Before each test, the table is truncated so that the index doesn’t get oversized and slow down the application running later in the test cycle.

Results

Each test is conducted with a total of 1 million requests. The following performance metrics are recorded for each test run:

It is important to note that resource consumption—specifically CPU and memory usage—is as critical as raw performance. A faster runtime that incurs significantly higher resource costs may not provide practical value in real-world scenarios.

As Node.js is running in cluster mode, the CPU and memory usage are cumulative for all processes in the cluster.

The results are as follows:

The same results in a concise tabular form are as follows:

Analysis

The conclusion is a bit tricky.

First, Node.js (in cluster mode) works slightly better than Go for this application. Unfortunately, SpringBoot (with virtual threads) is quite slow. However, we might argue that SpringBoot is a very heavy framework compared to minuscule Express and Gin.

The CPU usage for the Node.js application confirms that cores are getting utilized correctly. The CPU usage of the Go application is the lowest of all.

The place where Go truly excels is memory usage. It uses a mere 31M compared to 740M of Node.js and 315M of SpringBoot.

The overall winner is undoubtedly Go.