TIL: Java SpringBoot vs. Asp.Net REST API performance
Tags: dotnet, java, spring
Published: 2022-09-10 22:47:52

# TIL: Java Spring boot vs. Asp.Net REST API performance

Today I learned (#til) something about Java SpringBoot and Asp.Net performance.

I was testing an upcoming .Net 7 release and its Minimal APIs and I was amazed by the speed. So I decided to compare it to Java 18 SpringBoot project.

The finding was that Asp.Net 7 solution is almost 2x as fast as SpringBoot application on Java 18!

## Here are the results

Asp.Net 7 Minimal APIs delivers 4,084,163 requests in 30s compared to 2,152,070 request per 30s in case of SpringBoot Java 18 application!

**Java 18 SpringBoot**
```
$ k6 run --vus 16 --duration 30s config.json

          /\      |‾‾| /‾‾/   /‾‾/
     /\  /  \     |  |/  /   /  /
    /  \/    \    |     (   /   ‾‾\
   /          \   |  |\  \ |  (‾)  |
  / __________ \  |__| \__\ \_____/ .io

  execution: local
     script: config.json
     output: -

  scenarios: (100.00%) 1 scenario, 16 max VUs, 1m0s max duration (incl. graceful stop):
           * default: 16 looping VUs for 30s (gracefulStop: 30s)


running (0m30.0s), 00/16 VUs, 2152070 complete and 0 interrupted iterations
default ✓ [======================================] 16 VUs  30s

     data_received..................: 373 MB  12 MB/s
     data_sent......................: 392 MB  13 MB/s
     http_req_blocked...............: avg=1.61µs   min=441ns   med=942ns    max=2.39ms p(90)=1.53µs   p(95)=1.75µs
     http_req_connecting............: avg=308ns    min=0s      med=0s       max=1.55ms p(90)=0s       p(95)=0s
     http_req_duration..............: avg=180.65µs min=114.4µs med=170.42µs max=4.11ms p(90)=202.21µs p(95)=216.99µs
       { expected_response:true }...: avg=180.65µs min=114.4µs med=170.42µs max=4.11ms p(90)=202.21µs p(95)=216.99µs
     http_req_failed................: 0.00%   ✓ 0            ✗ 2152070
     http_req_receiving.............: avg=22.51µs  min=4.59µs  med=20.95µs  max=3.95ms p(90)=32.44µs  p(95)=39.5µs
     http_req_sending...............: avg=6.3µs    min=2.63µs  med=5.61µs   max=3.03ms p(90)=8.87µs   p(95)=9.59µs
     http_req_tls_handshaking.......: avg=0s       min=0s      med=0s       max=0s     p(90)=0s       p(95)=0s
     http_req_waiting...............: avg=151.83µs min=92.24µs med=142.39µs max=3.43ms p(90)=172.73µs p(95)=187.11µs
     http_reqs......................: 2152070 71733.676043/s
     iteration_duration.............: avg=218.91µs min=138.9µs med=207.65µs max=4.54ms p(90)=244.87µs p(95)=263.15µs
     iterations.....................: 2152070 71733.676043/s
     vus............................: 16      min=16         max=16
     vus_max........................: 16      min=16         max=16
```
**Asp.Net 7**
```
$ k6 run --vus 16 --duration 30s config.json

          /\      |‾‾| /‾‾/   /‾‾/
     /\  /  \     |  |/  /   /  /
    /  \/    \    |     (   /   ‾‾\
   /          \   |  |\  \ |  (‾)  |
  / __________ \  |__| \__\ \_____/ .io

  execution: local
     script: config.json
     output: -

  scenarios: (100.00%) 1 scenario, 16 max VUs, 1m0s max duration (incl. graceful stop):
           * default: 16 looping VUs for 30s (gracefulStop: 30s)


running (0m30.0s), 00/16 VUs, 4084163 complete and 0 interrupted iterations
default ✓ [======================================] 16 VUs  30s

     data_received..................: 845 MB  28 MB/s
     data_sent......................: 743 MB  25 MB/s
     http_req_blocked...............: avg=1.06µs   min=431ns   med=972ns    max=2.48ms p(90)=1.41µs   p(95)=1.62µs
     http_req_connecting............: avg=0ns      min=0s      med=0s       max=66.6µs p(90)=0s       p(95)=0s
     http_req_duration..............: avg=74.37µs  min=30.59µs med=67.84µs  max=7.33ms p(90)=91.37µs  p(95)=106.97µs
       { expected_response:true }...: avg=74.37µs  min=30.59µs med=67.84µs  max=7.33ms p(90)=91.37µs  p(95)=106.97µs
     http_req_failed................: 0.00%   ✓ 0             ✗ 4084163
     http_req_receiving.............: avg=13.64µs  min=4.43µs  med=12.72µs  max=7.05ms p(90)=17.75µs  p(95)=19.44µs
     http_req_sending...............: avg=6.36µs   min=2.77µs  med=5.96µs   max=5.83ms p(90)=8.24µs   p(95)=8.96µs
     http_req_tls_handshaking.......: avg=0s       min=0s      med=0s       max=0s     p(90)=0s       p(95)=0s
     http_req_waiting...............: avg=54.35µs  min=19.55µs med=48.45µs  max=5.34ms p(90)=70.1µs   p(95)=83.96µs
     http_reqs......................: 4084163 136135.973692/s
     iteration_duration.............: avg=113.11µs min=56.58µs med=105.71µs max=7.9ms  p(90)=133.41µs p(95)=153.52µs
     iterations.....................: 4084163 136135.973692/s
     vus............................: 16      min=16          max=16
     vus_max........................: 16      min=16          max=16
```

## Details

The goal was to test simple HTTP POST API that takes JSON body, deserializes it, modifies the values and returns serialized JSON response back.

The request and response objects are identical but represented by different classes. Modification of data is simple concatenation of strings and addition to integer value.

For Asp.Net project a preview7 of .Net 7 was used, for Java counterpart a new SpringBoot project with `spring-boot-starter-web` and `lombok` as dependencies combined with Oracle OpenJDK 18.0.2.1

The code is as follows:

## Java 18 SpringBoot
**pom.xml**
```
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>RestApiDemo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>RestApiDemo</name>
    <description>RestApiDemo</description>
    <properties>
        <java.version>18</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
```

**RestApiDemoApplication.java**
```
package com.example.restapidemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RestApiDemoApplication {

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

}
```

**InputModel.java**
```
package com.example.restapidemo;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@AllArgsConstructor
public final class InputModel {
    String FirstName;
    String LastName;
    Integer Age;
}
```

**OutputModel.java**
```
package com.example.restapidemo;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

@Setter
@Getter
@AllArgsConstructor
public final class OutputModel {
    String FirstName;
    String LastName;
    Integer Age;
}
```

**DemoController.java**
```
package com.example.restapidemo;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoController {

    @PostMapping("/get")
    @ResponseBody
    public OutputModel GetData(@RequestBody InputModel input) {
        return new OutputModel(input.FirstName + "x", input.LastName + "x", input.Age + 10);
    }
}
```

## Asp.Net 7
**Program.cs**
```
using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapPost("/get", ([FromBody] InputModel input) => new OutputModel
	{
		FirstName = input.FirstName + "x",
		LastName = input.LastName + "x",
		Age = input.Age + 10
	}
);

app.Run();

internal sealed class InputModel
{
	public string FirstName { get; set; }
	public string LastName { get; set; }
	public int Age { get; set; }
}

internal sealed class OutputModel
{
	public string FirstName { get; set; }
	public string LastName { get; set; }
	public int Age { get; set; }
}
```

**K6.io config.json**
```
import http from 'k6/http';

export default function () {

  const url = 'http://localhost:8080/get';

  const payload = JSON.stringify({
    FirstName: 'John',
    LastName: 'Doe',
    Age: 33
  });

  const params = {
    headers: {
      'Content-Type': 'application/json',
    },
  };

  http.post(url, payload, params);
}
```

Testing first with CURL for response validity:
**Java 18 SpringBoot**
```
$ curl -X POST http://localhost:8080/get -H "content-type: application/json" -d "{\"FirstName\":\"John\",\"LastName\":\"Doe\",\"Age\":33}"

{"firstName":"Johnx","lastName":"Doex","age":43}%
```

**Asp.Net 7**
```
$ curl -X POST http://localhost:8080/get -H "content-type: application/json" -d "{\"FirstName\":\"John\",\"LastName\":\"Doe\",\"Age\":33}"

{"firstName":"Johnx","lastName":"Doex","age":43}%
```

It seems Kestrel server with .Net 7 does a pretty decent job handling simple APIs compared to default Tomcat Server with Java 18 based SpringBoot application.

Maybe someone can consider using this when deciding to implement simple REST API since the bare language is very similar until you decide to use some ORM framework or logging.