A learning-focused Java Spring Boot project that builds a resilient background task scheduler using PostgreSQL as the durable state machine. The long-term goal is to support delayed jobs, safe concurrent workers, retries, heartbeat-based crash recovery, and idempotent task submission.
This project is based on a distributed task scheduler architecture. The scheduler is designed around a simple but powerful idea: every background job lives in the database, and workers safely claim jobs by moving them through a strict lifecycle.
Target task lifecycle:
PENDING -> RUNNING -> COMPLETED
\
-> FAILED
The architecture is intended to provide:
- A REST API for submitting background tasks.
- PostgreSQL-backed task persistence.
- Idempotency protection for duplicate task submissions.
SKIP LOCKEDrow-level locking so multiple workers can safely poll for work.- A worker engine that executes task handlers.
- Heartbeats while tasks are running.
- A reaper process that recovers stuck or orphaned tasks.
- Retry handling and permanent failure after max retries.
The project currently has the foundation in place, but the full worker engine is not implemented yet.
Already implemented:
- Spring Boot application setup.
- REST endpoint for creating tasks.
Taskentity mapped to thetaskstable.TaskStatusenum withPENDING,RUNNING,COMPLETED, andFAILED.TaskRepositorywith a PostgreSQLSKIP LOCKEDquery for safely acquiring pending tasks.- Reaper queries for rescuing stale
RUNNINGtasks or marking exhausted tasks asFAILED. - A
TaskHandlerinterface. - A sample
GeneratePdfHandler. - Basic idempotency support through
idempotencyKey. - Flyway database migration for creating the
taskstable,task_statusenum, and scheduler indexes. - Repository methods for heartbeat updates, completion, retry, and permanent failure.
- Handler registry that maps task types to handler implementations.
- In-memory
BlockingQueuefor handing tasks from the poller to executors.
Not implemented yet:
- Worker poller that calls
TaskRepository.acquireTasks(...). - Execution engine using Java threads or virtual threads.
- Heartbeat updates while a task is running.
- Docker Compose setup for PostgreSQL and multiple worker nodes.
At the moment, the application flow is:
Client sends POST /api/tasks
|
v
TaskController receives request
|
v
TaskProducerService creates a Task entity
|
v
TaskRepository saves the task as PENDING
|
v
Task waits in PostgreSQL
The current code can create queued tasks, but it does not yet execute them. The missing piece is the worker engine that should poll the database, acquire pending tasks, call the correct TaskHandler, and update the task status afterward.
The producer API accepts task requests and stores them in PostgreSQL.
TaskController -> TaskProducerService -> TaskRepository
The repository contains the most important database query in the scheduler: the task acquisition query. It uses PostgreSQL FOR UPDATE SKIP LOCKED so multiple workers can safely claim different tasks without processing the same task at the same time.
The reaper is a scheduled cleanup process. It looks for tasks that are stuck in RUNNING state with an old heartbeat. Those tasks are either moved back to PENDING for retry or marked as FAILED when retries are exhausted.
Task handlers contain the actual business logic for each task type. For example, GeneratePdfHandler represents a fake PDF generation job. The handler exists, but no worker currently calls it.
The next implementation steps are:
- Add a scheduled worker poller.
- Add the task execution engine.
- Add heartbeat handling.
- Add Docker Compose for PostgreSQL and multiple app workers.
- Java 21
- Spring Boot
- Spring Web MVC
- Spring Data JPA
- PostgreSQL
- Lombok
- Maven
Resilient Java Spring Boot background task scheduler using PostgreSQL, row-level locking, retries, and crash recovery.