A distributed job scheduler built from scratch in Java 21 + Spring Boot. Grows from a single durable primitive into delayed tasks, cron, a high-throughput queue, and DAGs.
Status: Phase 3e complete — durable execution, cron, sharding, Raft consensus, and remote gRPC workers. See docs/design/architecture.md for the full design.
Everything is built on one primitive:
Run
F(args)at-least-once at-or-after timeT, survive crashes, and never double-run concurrently for the same logical job.
Three mechanisms provide correctness:
| Mechanism | Gap it closes |
|---|---|
SELECT … FOR UPDATE SKIP LOCKED |
Many schedulers, no double-dispatch, no coordinator |
| Fencing tokens | Stalled/zombie worker can't corrupt state |
| Idempotency keys | Duplicate effects on retry |
api → worker → scheduler-core → raft
| Module | What it is |
|---|---|
raft |
Raft consensus from scratch — zero Spring deps |
scheduler-core |
Framework-light core: model, stores, leasing, retry, cron, sharding |
worker |
Execution side: handler interface, virtual-thread pool, heartbeats |
api |
Spring Boot app: REST, gRPC, metrics, loop runner |
Requires Docker.
cp .env.example .env
docker compose up --buildStarts Postgres, the app (:8080), Prometheus (:9090), Grafana (:3000, admin/admin).
# Submit a job due in 5 seconds
curl -s -XPOST localhost:8080/jobs -H 'content-type: application/json' \
-d '{"jobType":"sleep","payload":{"ms":2000},"scheduleAt":"+5s"}'
# Watch it move SCHEDULED → LEASED → RUNNING → SUCCEEDED
curl -s localhost:8080/jobs/<jobId>/runs | jq
# Recurring every minute
curl -s -XPOST localhost:8080/jobs -H 'content-type: application/json' \
-d '{"jobType":"noop","cronExpr":"* * * * *"}'
# Two-node demo (node-a shards 0-7, node-b shards 8-15)
docker compose -f docker-compose.yml -f docker-compose.multi-node.yml up --buildBuilt-in handlers: noop, sleep ({ms}), failNTimes ({failTimes}).
| Method | Path | Description |
|---|---|---|
POST |
/jobs |
Submit a job (idempotent on key) |
GET |
/jobs/{id} |
Job definition |
GET |
/jobs/{id}/runs |
All runs |
GET |
/jobs/{id}/next-fire |
Next cron fire time |
DELETE |
/jobs/{id} |
Cancel non-terminal runs |
GET |
/actuator/health · /actuator/prometheus |
Health & metrics |
Requires JDK 21 and Docker (Testcontainers).
./gradlew build # compile + full test suite
./gradlew :api:bootJar # fat JAR → api/build/libs/| Role | What runs |
|---|---|
standalone (default) |
Scheduler + in-process worker, no gRPC |
scheduler |
Scheduler loops + gRPC dispatch to remote workers |
worker |
gRPC server only — receives and executes assignments |
| Area | Path |
|---|---|
| Design & architecture | docs/design/ |
| Implementation guide | docs/implementation/ |
| Testing | docs/testing/ |
| Architecture Decision Records | docs/adr/ |
| OpenAPI spec | docs/specs/openapi.yaml |
| Changelog | CHANGELOG.md |