A distributed job processing system built with Java 21, Spring Boot, RabbitMQ, and PostgreSQL.
Client (REST)
│
▼
API Service (Spring Boot :8080)
│
▼
RabbitMQ (job.exchange → job.queue ↔ job.dlq)
│
▼
Worker Service (Spring Boot :8081)
│
▼
PostgreSQL
- Java 21 (Zulu OpenJDK)
- Spring Boot 3.4.3 (Web, Data JPA, AMQP, Actuator, Retry)
- RabbitMQ 3.13 (with management UI)
- PostgreSQL 16
- Docker Compose
- Flyway (database migrations)
- Micrometer + Prometheus (observability)
- Java 21
- Maven 3.9+
- Docker & Docker Compose
docker-compose up --build- Start infrastructure:
docker-compose up postgres rabbitmq- Build:
mvn clean install -DskipTests- Run API Service:
mvn spring-boot:run -pl api-service- Run Worker Service (in another terminal):
mvn spring-boot:run -pl worker-service| Method | Endpoint | Description |
|---|---|---|
POST |
/api/v1/jobs |
Submit a new job |
GET |
/api/v1/jobs/{id} |
Get job details |
GET |
/api/v1/jobs |
List jobs (paginated) |
DELETE |
/api/v1/jobs/{id} |
Cancel a pending job |
GET |
/api/v1/jobs/{id}/attempts |
Get retry history |
curl -X POST http://localhost:8080/api/v1/jobs \
-H "Content-Type: application/json" \
-d '{
"type": "EMAIL",
"priority": "HIGH",
"payload": {
"to": "user@example.com",
"subject": "Welcome!",
"body": "Hello from TaskHive"
}
}'EMAIL— Sends an email notificationREPORT— Generates a report (PDF/CSV)DATA_EXPORT— Runs an ETL data export pipeline
Start the full stack including monitoring:
docker-compose up --build| Service | URL | Credentials |
|---|---|---|
| Grafana | http://localhost:3000 | admin / taskhive |
| Prometheus | http://localhost:9090 | — |
| RabbitMQ Management | http://localhost:15672 | taskhive / taskhive_secret |
| API Actuator | http://localhost:8080/actuator/health | — |
| Prometheus Metrics | http://localhost:8080/actuator/prometheus | — |
The Grafana dashboard loads automatically with 10 panels:
| Panel | Type | What It Shows |
|---|---|---|
| Jobs Submitted | Time series | Submission rate per minute |
| Jobs Completed | Time series | Completion rate per minute |
| Jobs Failed | Time series | Failure rate per minute |
| Total Jobs | Stat | Cumulative submitted / completed / failed counters |
| Job Success Rate | Gauge | Percentage (red < 80%, orange < 95%, green ≥ 95%) |
| Processing Duration | Time series | p50 and p95 execution time |
| HTTP Request Rate | Time series | API endpoint request rates by method/uri/status |
| HTTP Response Time | Time series | API endpoint latency p95 |
| JVM Heap Memory | Time series | Heap usage per service |
| DB Connection Pool | Time series | HikariCP active/idle connections |
Run all tests:
mvn test| Test Class | Tests | Coverage |
|---|---|---|
JobServiceTest |
9 | Submit (afterCommit), get, cancel (state validation), attempts |
JobControllerTest |
5 | HTTP 201/200/404/409, JSON response validation |
JobExecutorTest |
5 | Strategy resolution, success/failure, DLQ on max retries, metrics |
JobListenerTest |
4 | Execute, skip cancelled, skip not-found, throw for DLQ retry |
DeadLetterListenerTest |
3 | Retry with increment, drop on exhaust, boundary condition |
EmailJobHandlerTest |
3 | Correct type, execution result, missing fields graceful handling |
RabbitMQ messages are published after the database transaction commits using TransactionSynchronizationManager.afterCommit(). This prevents a race condition where the worker receives a job message before the job row is visible in PostgreSQL — a common gotcha in async systems with transactional writes + message queues.
Spring Boot 3.4.x deprecated @MockBean in favor of @MockitoBean (org.springframework.test.context.bean.override.mockito). Controller tests use standalone MockMvcBuilders.standaloneSetup() instead of @WebMvcTest to avoid auto-configuring JPA/RabbitMQ during test context loading.
TaskHive/
├── common/ (shared entities, DTOs, config)
├── api-service/ (REST API + job producer)
├── worker-service/ (job consumer + execution engine)
├── monitoring/
│ ├── prometheus/ (scrape config)
│ └── grafana/ (provisioning + dashboard JSONs)
├── docker-compose.yml
└── pom.xml (parent POM)