SpringBoot vs Go Frameworks: Hello World Performance

Mayank C
Tech Tonic

--

This is a requested article. Readers have asked for an up-to-date comparison of Spring Boot with virtual threads and popular Go frameworks like Gin, Fiber, and Echo. Please note that this is not an apples-to-apples comparison, as Spring Boot is a comprehensive framework with an extensive feature set, whereas the mentioned Go frameworks are relatively simple (similar to Express).

In this article, we will focus on the simplest “Hello World” use case. We acknowledge that a “Hello World” example is far from real-world scenarios and not ideal for showcasing the capabilities of Java virtual threads. However, this comparison aims to provide a basic contrast between the two, rather than demonstrating the advantages of virtual threads. We will follow up with another article that will perform database reads (I/O-bound operations), which will better utilize and showcase the power of virtual threads.

A similar comparison with Quarkus can be seen here:

A more real-world comparison can be seen here:

Test setup

All tests have been executed on MacBook Pro M2 with 16G RAM & 8+4 CPU cores. The software versions are:

  • SpringBoot 3.2.5 (Java 21.0.3)
  • Go 1.22.3

The tests are conducted using Bombardier load tester.

The application code is as follows:

SpringBoot

// application.properties

server.port=3000
spring.threads.virtual.enabled=true

// HelloWorldApplication.java

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer;
import org.springframework.context.annotation.Bean;
import java.util.concurrent.Executors;

@SpringBootApplication
@RestController
public class HelloWorldApplication {

public static void main(String[] args) {
SpringApplication.run(HelloWorldApplication.class, args);
}

@GetMapping("/")
public String handleRequest() {
return "Hello World!";
}
}

Gin

package main

import (
"net/http"

"github.com/gin-gonic/gin"
)

func main() {
r := gin.New()

r.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "Hello world!")
})

r.Run(":3000")
}

Fiber

package main

import (
"github.com/gofiber/fiber/v2"
)

func main() {
app := fiber.New()
port := ":3000"

app.Get("/", func(c *fiber.Ctx) error {
return c.SendString("Hello World!")
})

app.Listen(port)
}

Echo

package main

import (
"net/http"

"github.com/labstack/echo/v4"
)

func main() {
e := echo.New()
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello World!")
})
e.Start(":3000")
}

Results

The test is carried out for 100 concurrent connections, running for a total of 10M requests.

The results in chart form as follows:

Analysis

Firstly, Spring Boot’s results are skewed due to some outliers. The maximum latency reaches up to 20 seconds, which distorts the charts for mean time taken, requests per second, and other measurements.

All Go frameworks complete the task (10 million requests) in less than half the time taken by Spring Boot. The latency numbers are relatively comparable, except for the maximum latency, where Spring Boot performs poorly.

The CPU usage of Spring Boot is lower than all the Go frameworks, while the memory usage is significantly higher (500MB vs 30MB).

Despite this, Spring Boot performs remarkably well considering the extensive features it provides out of the box. However, we must consider the numbers to derive a fair conclusion.

Winner: GIN

--

--