Side-by-side stock price aggregators for comparing Java 21 virtual threads + Spring Boot vs Kotlin coroutines + Ktor on the same live-data workload.
Both services pull prices from Yahoo Finance and Stooq, apply per-provider:symbol retry + circuit breaker, keep an in-memory history ring buffer, and expose identical REST + WebSocket APIs plus a Chart.js dashboard.
| Module | Stack | Default port |
|---|---|---|
kotlin-aggregator/ |
Ktor + coroutines + kotlinx.serialization | 8080 |
java-aggregator/ |
Spring Boot 3.4 + virtual threads + Jackson | 8081 |
Design background: stock-aggregator-java-vs-kotlin.md.
Docs: index · dual-run runbook · Kotlin runbook · Java runbook · simultaneous analysis guide
- JDK 21+
- Outbound HTTPS to
query1.finance.yahoo.comandstooq.com
# Terminal 1 — Kotlin
./gradlew :kotlin-aggregator:run
# Terminal 2 — Java
./gradlew :java-aggregator:bootRunThen open:
- Kotlin dashboard: http://localhost:8080
- Java dashboard: http://localhost:8081
curl http://localhost:8080/prices
curl http://localhost:8081/pricesFull dual-run steps (identical env, warm-up, clean stop): docs/runbooks/dual-run.md.
How to benchmark both at once: docs/ANALYSIS.md.
| Endpoint | Purpose |
|---|---|
GET / |
Chart.js dashboard |
GET /symbols |
Configured tickers |
GET /prices |
All latest aggregated prices |
GET /prices/{symbol} |
One symbol (404 if none yet) |
GET /prices/{symbol}/history |
In-memory history ring buffer |
WS /ws/prices |
Push every update as JSON (seeds cache on connect) |
| Variable | Default | Meaning |
|---|---|---|
SYMBOLS |
AAPL,MSFT,GOOG |
Comma-separated tickers |
POLL_INTERVAL_MS |
5000 |
Per-symbol poll interval |
PROVIDER_TIMEOUT_MS |
3000 |
Per-attempt provider timeout |
MAX_RETRIES |
2 |
Extra attempts with exponential backoff |
RETRY_BASE_DELAY_MS |
200 |
Backoff base (200, 400, 800, ...) |
HISTORY_SIZE |
200 |
Max history points per symbol |
CB_FAILURE_THRESHOLD |
3 |
Failures before circuit opens |
CB_RESET_TIMEOUT_MS |
30000 |
Open-circuit cool-down |
PORT |
8080 (Kotlin) / 8081 (Java) |
HTTP/WebSocket port |
Example:
SYMBOLS=AAPL,TSLA,NVDA POLL_INTERVAL_MS=10000 ./gradlew :kotlin-aggregator:run
PORT=8081 SYMBOLS=AAPL,TSLA,NVDA POLL_INTERVAL_MS=10000 ./gradlew :java-aggregator:bootRun| Concern | Kotlin | Java |
|---|---|---|
| Concurrency | supervisorScope + coroutines + withTimeoutOrNull |
Virtual-thread executor + blocking HTTP + futures/timeouts |
| HTTP server | Ktor Netty | Spring MVC (+ VT-enabled request threads) |
| Serialization | kotlinx.serialization | Jackson |
| Fan-out | SharedFlow → WebSocket |
Listener/ConcurrentHashMap of sessions → WebSocket |
./gradlew :kotlin-aggregator:test :java-aggregator:test
./gradlew buildProgress is tracked with beads (bd). Epic: Java vs Kotlin stock price aggregators.