A production-grade stock exchange simulator built with Java 21, Spring Boot 3, PostgreSQL, Redis and Kafka.
Client (React)
│
Nginx API Gateway ← JWT auth, rate limiting, routing
│
┌───┴───────────────────────────────────────┐
│ Core Services │
│ ├── User Service :8081 │
│ ├── Order Service :8082 │
│ ├── Portfolio Service :8084 │
│ └── Market Data Svc :8085 │
└───────────────┬───────────────────────────┘
│
Matching Engine :8083
(one virtual thread per symbol)
│
Kafka Event Bus
├── Portfolio Updater
├── Market Data Updater
└── Trade Logger
│
WebSocket Gateway :8086 → Client
| Layer | Technology |
|---|---|
| Language | Java 21 (virtual threads via Project Loom) |
| Framework | Spring Boot 3.2 |
| Database | PostgreSQL 16 |
| Cache / Pub-Sub | Redis 7 |
| Message Bus | Kafka (Confluent) |
| Auth | JWT (JJWT 0.12) |
| Build | Maven (multi-module) |
| Observability | Prometheus + Grafana |
| Containers | Docker Compose |
- Java 21+
- Maven 3.9+
- Docker + Docker Compose
docker compose up -dThis starts: PostgreSQL, Redis, Kafka, Kafka UI, Prometheus, Grafana.
Wait for health checks to pass:
docker compose pscd services/user-service
mvn spring-boot:runService starts on http://localhost:8081
# Register
curl -X POST http://localhost:8081/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"trader@example.com","username":"trader1","password":"password123"}'
# Login
curl -X POST http://localhost:8081/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"trader@example.com","password":"password123"}'
# Get profile (replace TOKEN)
curl http://localhost:8081/api/v1/auth/me \
-H "Authorization: Bearer TOKEN"# All tests
mvn test
# Single service
cd services/matching-engine && mvn testtrading-simulator/
├── pom.xml ← Parent POM (all shared deps)
├── docker-compose.yml ← Full local infra
│
├── infra/
│ ├── sql/init.sql ← DB schema + seed data
│ └── prometheus/prometheus.yml
│
└── services/
├── user-service/ ← Auth, JWT, user profiles
├── order-service/ ← Order lifecycle, validation
├── matching-engine/ ← Core: order book, trade execution
├── portfolio-service/ ← Holdings, PnL tracking
├── market-data-service/ ← Candles, order book depth
├── websocket-gateway/ ← Live streaming to clients
└── trading-bots/ ← Random, Momentum, Mean Reversion bots
- User Service — auth foundation, no dependencies
- Matching Engine — pure Java, unit test heavily first
- Order Service — connects to matching engine
- Portfolio Service — consumes TradeExecuted events
- Market Data Service — candles, order book feed
- WebSocket Gateway — live streaming
- Trading Bots — market simulation
One virtual thread per symbol. The OrderBook class is single-threaded by design.
Race conditions are impossible because only one thread ever accesses a given symbol's book.
All portfolio and market data updates happen by consuming Kafka events, not via direct service calls. This keeps services loosely coupled and allows replay on restart.
On startup, the matching engine queries pending_orders from PostgreSQL and rebuilds
the in-memory order books. No state is lost on crash.
All services use spring.threads.virtual.enabled=true.
The matching engine runs one virtual thread per symbol — lightweight, no thread pool sizing needed.
| Service | URL |
|---|---|
| Kafka UI | http://localhost:8090 |
| Grafana | http://localhost:3000 (admin/admin123) |
| Prometheus | http://localhost:9090 |
| PostgreSQL | localhost:5432 (tradingsim/tradingsim123) |
| Redis | localhost:6379 (password: redis123) |