Performance benchmark suite for comparing the Java (Spring Boot) and Go (net/http) implementations of the Expense Manager API.
🤖 Generated with Claude Opus 4.6 (Thinking) via Antigravity — Google DeepMind's advanced agentic coding assistant.
- Overview
- Prerequisites
- Project Structure
- Quick Start
- Running Benchmarks
- Test Scenarios
- Custom Metrics
- Thresholds
- Configuration
- Viewing Results
This benchmark uses k6 to load-test the Expense Manager REST API. It exercises the full CRUD lifecycle (Create → Read → Update → Delete) along with category filtering and summary endpoints, across three progressively intense scenarios: smoke, load, and stress.
| Tool | Install Command | Purpose |
|---|---|---|
| k6 | brew install k6 |
Load testing framework |
| jq (optional) | brew install jq |
Pretty-print JSON results |
| curl | Comes with macOS/Linux | Health-check services |
Ensure at least one of the target services is running:
| Service | Default URL | How to Start |
|---|---|---|
| Java | http://localhost:8080 |
cd ../expense-manager-java && mvn spring-boot:run |
| Go | http://localhost:8081 |
cd ../expense-manager-go && go run . |
k6-benchmark/
├── README.md # This file
├── benchmark.js # k6 test script (scenarios, checks, custom metrics)
└── run_benchmark.sh # Convenience shell wrapper
# 1. Start the target service(s)
cd ../expense-manager-java && mvn spring-boot:run # terminal 1
cd ../expense-manager-go && go run . # terminal 2
# 2. Run benchmarks
cd k6-benchmark
chmod +x run_benchmark.sh
./run_benchmark.sh # benchmarks both services# Benchmark both Java and Go services
./run_benchmark.sh
# Benchmark Java only
./run_benchmark.sh java
# Benchmark Go only
./run_benchmark.sh goThe wrapper script will:
- Verify that
k6is installed - Health-check the target service(s)
- Run the benchmark
- Save results to
results_java.jsonand/orresults_go.json
# Against the Java service
k6 run -e SERVICE=java -e JAVA_URL=http://localhost:8080 benchmark.js
# Against the Go service
k6 run -e SERVICE=go -e GO_URL=http://localhost:8081 benchmark.jsIf your services run on non-default ports or hosts:
JAVA_URL=http://myhost:9090 GO_URL=http://myhost:9091 ./run_benchmark.shOr with k6 directly:
k6 run -e SERVICE=java -e JAVA_URL=http://myhost:9090 benchmark.jsThe benchmark runs three scenarios sequentially:
| Scenario | Type | VUs | Duration | Start Time | Purpose |
|---|---|---|---|---|---|
| Smoke | Constant VUs | 1 | 10s | 0s | Quick sanity check — does the API work? |
| Load | Ramping VUs | 0 → 20 → 20 → 0 | 55s | 15s | Sustained moderate load |
| Stress | Ramping VUs | 0 → 50 → 100 → 0 | 70s | 70s | Push limits under heavy concurrency |
Each virtual user iteration performs the following API operations:
- Create an expense (
POST /api/expenses) - List all expenses (
GET /api/expenses) - Filter by category (
GET /api/expenses?category=...) - Get by ID (
GET /api/expenses/{id}) - Update the expense (
PUT /api/expenses/{id}) - Delete the expense (
DELETE /api/expenses/{id}) - Verify deletion returns 404 (
GET /api/expenses/{id}) - Get summary (
GET /api/expenses/summary)
Beyond k6's built-in metrics, the benchmark tracks:
| Metric | Type | Description |
|---|---|---|
create_latency |
Trend | Time to create an expense |
get_all_latency |
Trend | Time to list / filter expenses |
get_by_id_latency |
Trend | Time to fetch a single expense |
update_latency |
Trend | Time to update an expense |
delete_latency |
Trend | Time to delete an expense |
summary_latency |
Trend | Time to fetch the summary endpoint |
total_requests |
Counter | Total API requests made |
errors |
Rate | Percentage of failed checks |
The benchmark will report a failure if any of these thresholds are breached:
| Metric | Threshold |
|---|---|
http_req_duration p(95) |
< 500 ms |
http_req_duration p(99) |
< 1000 ms |
errors rate |
< 10 % |
create_latency p(95) |
< 300 ms |
get_all_latency p(95) |
< 200 ms |
get_by_id_latency p(95) |
< 150 ms |
update_latency p(95) |
< 300 ms |
delete_latency p(95) |
< 200 ms |
summary_latency p(95) |
< 200 ms |
All configuration is done via environment variables:
| Variable | Default | Description |
|---|---|---|
SERVICE |
java |
Which service to target (java or go) |
JAVA_URL |
http://localhost:8080 |
Base URL for the Java service |
GO_URL |
http://localhost:8081 |
Base URL for the Go service |
k6 prints a rich summary to the terminal after each run, including all custom metrics and threshold pass/fail status.
Detailed results are saved as JSON files in the working directory:
# Pretty-print Java results
jq . results_java.json
# Pretty-print Go results
jq . results_go.json
# Side-by-side comparison
jq . results_java.json results_go.json{
"service": "java",
"baseUrl": "http://localhost:8080",
"timestamp": "2026-06-01T12:00:00.000Z",
"metrics": {
"http_req_duration": { "avg": 12.5, "p90": 25.0, "p95": 30.0, "p99": 50.0, "max": 100.0 },
"create_latency": { "avg": 15.0, "p95": 35.0 },
"get_all_latency": { "avg": 8.0, "p95": 18.0 },
"get_by_id_latency": { "avg": 5.0, "p95": 12.0 },
"update_latency": { "avg": 14.0, "p95": 32.0 },
"delete_latency": { "avg": 6.0, "p95": 15.0 },
"summary_latency": { "avg": 7.0, "p95": 16.0 },
"total_requests": 5000,
"error_rate": 0.001,
"http_reqs": 5000,
"http_req_rate": 120.5
}
}This project is provided as-is for benchmarking and educational purposes.
Results generated on macOS, using local k6 load test (Smoke + Load + Stress scenarios).
| Metric | Java | Go | Winner |
|---|---|---|---|
| HTTP Req Duration (avg) | 2.31 ms | 2.16 ms | Go |
| HTTP Req Duration (p95) | 5.80 ms | 6.42 ms | Java |
| Create Latency (avg) | 2.35 ms | 2.11 ms | Go |
| Create Latency (p95) | 5.68 ms | 6.22 ms | Java |
| Get All Latency (avg) | 2.61 ms | 2.57 ms | Go |
| Get All Latency (p95) | 6.47 ms | 7.55 ms | Java |
| Get By ID Latency (avg) | 2.23 ms | 1.99 ms | Go |
| Get By ID Latency (p95) | 5.60 ms | 5.79 ms | Java |
| Update Latency (avg) | 2.30 ms | 2.11 ms | Go |
| Update Latency (p95) | 5.61 ms | 6.39 ms | Java |
| Delete Latency (avg) | 2.14 ms | 2.08 ms | Go |
| Delete Latency (p95) | 5.43 ms | 6.04 ms | Java |
| Summary Latency (avg) | 2.08 ms | 1.96 ms | Go |
| Summary Latency (p95) | 5.29 ms | 5.75 ms | Java |
| Total Requests | 42,312 | 42,288 | Tie |
| Throughput (Req/sec) | ~300.36 | ~300.01 | Tie |
Observation: Go exhibits slightly lower average latency across most endpoints, suggesting lower overhead for typical requests. However, Java exhibits a tighter latency distribution, beating Go at the 95th percentile (p95), which suggests more consistent tail-latency under the load tested.
To find the true breaking point of both services, an aggressive Breakpoint Test was run, ramping up to 1,500 concurrent Virtual Users.
| Metric | Java (Spring Boot) | Go (Gin Framework) | Winner |
|---|---|---|---|
| HTTP Req Duration (avg) | ~36.11 ms | ~7.36 ms | Go |
| HTTP Req Duration (p95) | ~153.63 ms | ~38.62 ms | Go |
| Create Latency (avg) | ~49.72 ms | ~7.00 ms | Go |
| Create Latency (p95) | ~213.48 ms | ~38.05 ms | Go |
| Get All Latency (avg) | ~44.97 ms | ~10.30 ms | Go |
| Get All Latency (p95) | ~197.28 ms | ~52.00 ms | Go |
| Get By ID Latency (avg) | ~31.85 ms | ~6.19 ms | Go |
| Get By ID Latency (p95) | ~120.31 ms | ~33.74 ms | Go |
| Update Latency (avg) | ~30.04 ms | ~6.37 ms | Go |
| Update Latency (p95) | ~112.46 ms | ~33.18 ms | Go |
| Delete Latency (avg) | ~28.36 ms | ~6.41 ms | Go |
| Delete Latency (p95) | ~108.37 ms | ~34.93 ms | Go |
| Summary Latency (avg) | ~30.57 ms | ~6.10 ms | Go |
| Summary Latency (p95) | ~113.30 ms | ~32.66 ms | Go |
| Total Requests Processed | 81,169 | 130,003 | Go |
| Error Rate | 0% | 0% | Tie |
| Max Throughput (Req/sec) | ~1,014.21 | ~1,623.11 | Go |
| Max RPM (Req/min) | ~60,853 RPM | ~97,386 RPM | Go |
Conclusion: When refactored to use the production-ready gin-gonic/gin framework instead of the standard library's raw net/http mux, Go demonstrates vastly superior performance and extreme resilience. It serves nearly 100,000 requests per minute out of the box in a highly constrained Docker environment (250MB Memory, 1.0 CPU), significantly outperforming Java in both raw throughput and tail latency under extreme load.