Scheduled and deferred jobs
View MarkdownVertique’s job modules run background work when it should run, either recurring on a cron schedule or deferred to a future time. Retry, dead-letter handling, and cluster-aware coordination are built in, and jobs run in memory or durably on PostgreSQL.
- Cron scheduling from an annotation. Mark a service method with
@CronJob, or register a config-only entry with no code. Expressions have six fields with seconds precision and run in the job’s configured timezone. - Run once per cluster.
SINGLE_INSTANCEmode elects exactly one node per fire time through a database unique-index race, without separate leader-election infrastructure. - Misfire recovery. Fires missed while every node was down are recovered by policy: fire the most recent, skip them all, or fire each in order.
- A durable deferred queue. A delayed job runs once at or after a future time and survives restarts. It can be enqueued inside your business transaction, where it commits or rolls back with the write it accompanies.
- Retry and dead-letter built in. Fixed, linear, or exponential backoff applies up to a maximum attempt count. After that the job moves to a dead-letter state and is not silently lost.
- Typed job contracts. A pair of linked interfaces separates the enqueue API from the execution logic, with generated proxies keeping enqueueing compile-time typed.
- Cluster-aware coordination. The job coordinator tracks node heartbeats and recovers orphaned executions across the cluster.
Reach for a job when the work is a single handler invocation on a schedule or after a delay. A multi-step instance that waits on a signal or a human decision is a durable workflow, and handing a transactional side effect to another system is inbox/outbox messaging. A job handler can still use inbox/outbox internally.
Define a typed job
Section titled “Define a typed job”A typed job contract separates the enqueue API from the execution logic using a pair of linked
interfaces. From the public vertique-example-webhook example:
@DelayedJobContract(name = "deliver-webhook", maxAttempts = 2)public interface DeliverWebhookJob extends DelayedJobClient<WebhookPayload> {}
@Singletonpublic class DeliverWebhookJobImpl implements DelayedJobExecutor<WebhookPayload, DeliverWebhookJob> {
private final WebhookClient webhookClient;
@Inject DeliverWebhookJobImpl(WebhookClient webhookClient) { this.webhookClient = webhookClient; }
@Override public Future<Void> execute(WebhookPayload payload, JobContext ctx) { ctx.logger().info("Delivering webhook for payment " + payload.paymentId()); return webhookClient.notify(payload.paymentId()); }}To call it, inject the contract interface anywhere and enqueue with a typed payload: immediately, at a time, after a delay, or atomically inside an existing database transaction.
job.enqueue(new WebhookPayload("pay_123")); // run as soon as possiblejob.enqueue(payload, Duration.ofMinutes(5)); // run five minutes from nowjob.enqueue(payload, runAt); // run at a specific instantjob.enqueue(payload, tx); // commit or roll back with the business transaction@DelayedJobContract names the contract and its default enqueue parameters (maxAttempts,
queue, priority). The executor is discovered at startup from its own generic type arguments,
so it needs no separate registration call. The vertique-job-delayed module reference documents
the wiring, generated proxies, and runtime overrides in full.
Cron scheduling
Section titled “Cron scheduling”Mark a service implementation method with @CronJob to register a recurring job. From the
vertique-job-cron module reference’s example:
public class CleanupServiceImpl implements CleanupService {
@CronJob(id = "cleanup-expired", cron = "0 0 2 * * *", overlapPolicy = OverlapPolicy.SKIP) @Override public Future<Void> cleanupExpired() { return repository.deleteExpiredRecords(); }
@CronJob( id = "daily-report", cron = "0 0 8 * * *", mode = ExecutionMode.SINGLE_INSTANCE, timezone = "Europe/Helsinki", tracked = "true", misfirePolicy = MisfirePolicy.FIRE_NOW ) @Override public Future<Void> generateDailyReport() { return reportService.generate(); }}Cron expressions use a 6-field format with seconds precision: 0 0 8 * * * fires daily at
08:00 in the job’s timezone (UTC by default). SINGLE_INSTANCE runs a job on exactly one node
per cluster per fire time (it requires vertique-job-postgresql), and the misfire policy decides
what happens to fires missed while every node was down. A config-only job is a named entry under
the cron.jobs config subtree with no matching annotation. It registers the same way and targets
a service or an event bus address. The vertique-job-cron module reference documents the
expression format, execution modes, overlap policies, and misfire recovery in full.
Delayed and deferred execution
Section titled “Delayed and deferred execution”vertique-job-delayed is a persistent, database-backed queue. Enqueued work survives restarts
and becomes eligible at its scheduled time. It is dispatched over the event bus to its handler
with the configured backoff (fixed, linear, or exponential) up to a maximum attempt count before
moving to a dead-letter state. Enqueueing inside an existing transaction commits or rolls back
with the business write it accompanies. The vertique-job-delayed module reference documents the
claim mechanics, retry configuration, and outcome reporting in full.
The job modules
Section titled “The job modules”vertique-job-core defines the shared job state machine, the JobContext API a handler uses to
report progress and check cancellation, the JobRepository and JobInterceptor SPIs, and
JobCoordinator, which tracks node heartbeats and recovers orphaned executions across a cluster.
Two triggers build on it: vertique-job-cron schedules recurring executions, and
vertique-job-delayed is the persistent deferred queue.
| Add | For |
|---|---|
vertique-job-core |
The state machine, JobContext, and the coordinator every job scheduling module shares |
vertique-job-cron |
Recurring executions from @CronJob methods or config; runs in memory unless paired with vertique-job-postgresql |
vertique-job-delayed |
Deferred, durable execution with retry and dead-letter; always requires vertique-job-postgresql |
vertique-job-postgresql |
The JobRepository implementation backing durable cron scheduling and every delayed job |
vertique-job-cron can run without persistence: install vertique-job-core and vertique-job-cron
alone, and cron scheduling stays in memory on each node. vertique-job-delayed has no such
in-memory mode. It depends directly on vertique-job-postgresql, so a delayed job’s state is
always durable and visible across the cluster once it is enqueued. There is no
vertique-starter-job. An application composes these artifacts directly into its own Dagger
component.
vertique-job-postgresql wires the database connection layer directly and does not go through a
starter. It depends on vertique-db-postgresql, and the application still installs both
DbPostgresqlModule and DbFlywayModule in its own Dagger component. The adapter does not run
Flyway migration for the application, but it ships its migration scripts at
classpath:db/migration/job and a JobMigrationsModule that declares them as a migration named
job. With the opt-in named mode (flyway.instances), that declaration gives the job tables their
own Flyway history, and an optional db.schemas.job setting places them in their own schema for
both the migration and the repository’s SQL. In the default single-history mode, flyway.locations
must instead list the job path alongside the application’s own migration location for the scripts
to run. An application that already uses the PostgreSQL starter for its own repositories (see
Persistence) keeps that starter and composes the job PostgreSQL adapter
alongside it. The module’s own reference documents the wiring. See Durable
workflows for the same pattern stated for
the workflow and inbox/outbox adapters.
Learn more
Section titled “Learn more”The module references below document the constructs, SQL, configuration, and extension points in full. This page does not repeat them.
vertique-job-coremodule reference: the shared job state machine,JobContext, andJobCoordinator.vertique-job-cronmodule reference: recurring@CronJobscheduling, misfire recovery, andSINGLE_INSTANCEleader election.vertique-job-delayedmodule reference: the durable, retryable deferred queue and typed job contracts.vertique-job-postgresqlmodule reference: theJobRepositoryimplementation.DeliverWebhookJob: the typed job contract the excerpt above comes from, in thevertique-example-webhookexample.- Durable workflows
- Transactional inbox/outbox messaging
- Persistence
- Application model
- What is Vertique?
Continue reading
Section titled “Continue reading”- Previous: Transactional inbox/outbox
- Next: Workflows