JobSchedulerService
The primary entry point for all job scheduling operations. Inject this interface to enqueue jobs, create batches, schedule recurring work, and manage the job lifecycle.
@Inject
JobSchedulerService scheduler;Package: run.ratchet.apiType: Interface
Scheduling methods
enqueue
JobBuilder enqueue(SerializableCheckedRunnable task)Enqueues a task for immediate execution, returning a JobBuilder for further configuration. The task is not persisted until submit() is called on the returned builder.
Parameters:
task-- the job task to execute. Must be a single method reference or single method call (see method reference constraint).
Returns: a JobBuilder for configuring retries, priority, timeout, workflows, tags, parameters, and callbacks.
// Simple enqueue with configuration
JobHandle handle = scheduler.enqueue(() -> emailService.send(userId))
.withPriority(JobPriority.HIGH)
.withMaxRetries(3)
.withBackoff(BackoffPolicy.EXPONENTIAL, Duration.ofSeconds(5))
.withTimeout(Duration.ofMinutes(10))
.withTags("email", "notifications")
.withParam("userId", userId)
.submit();enqueueNow
JobHandle enqueueNow(SerializableCheckedRunnable task)Enqueues a task for immediate execution with default configuration. This is a convenience method equivalent to enqueue(task).immediate().submit().
Parameters:
task-- the job task to execute.
Returns: a JobHandle containing the assigned UUIDv7 job ID.
// Fire-and-forget with default settings
JobHandle handle = scheduler.enqueueNow(() -> auditService.log(event));
UUID jobId = handle.id();schedule
JobBuilder schedule(Duration delay, SerializableCheckedRunnable task)Schedules a task to execute after the specified delay. Returns a JobBuilder for further configuration.
Parameters:
delay-- how long to wait before the job becomes eligible for execution. Must not be null.task-- the job task to execute.
Returns: a JobBuilder for further configuration.
// Send a reminder email in 24 hours
scheduler.schedule(Duration.ofHours(24), () -> reminderService.send(orderId))
.withParam("orderId", String.valueOf(orderId))
.withTags("reminder")
.submit();scheduleRecurring
RecurringJobBuilder scheduleRecurring(
String cron, ZoneId zone, SerializableCheckedRunnable task)Schedules a recurring job based on a cron expression. Returns a RecurringJobBuilder for configuring options, tags, identity, and downtime behavior.
Parameters:
cron-- Quartz cron expression (6-7 fields:second minute hour day-of-month month day-of-week [year]).zone-- timezone for evaluating the cron expression.task-- the job task to execute on each occurrence.
Returns: a RecurringJobBuilder for configuring options, tags, business key, and a persisted RecurringMisfirePolicy.
// Every weekday at 9 AM Eastern
scheduler.scheduleRecurring(
"0 0 9 ? * MON-FRI",
ZoneId.of("America/New_York"),
() -> reportService.generateDailyReport())
.withOptions(JobOptions.defaults()
.withMaxRetries(2)
.withBackoff(BackoffPolicy.FIXED, Duration.ofMinutes(5)))
.withTags(List.of("reports", "daily"))
.withBusinessKey("daily-report")
.withMisfirePolicy(RecurringMisfirePolicy.fireOnce())
.submit();withMisfirePolicy accepts skip(), fireOnce(), or catchUp(maxExecutions). The default is catchUp(11), which preserves the previous bounded catch-up behavior. Policies apply only when at least two occurrences are overdue; a lone due occurrence runs normally.
Batch methods
enqueueBatch
BatchBuilder enqueueBatch(String name)Creates a BatchBuilder for parallel execution of multiple tasks. The batch is submitted as a single unit and provides aggregate progress tracking.
Parameters:
name-- human-readable name for the batch, used in logs and monitoring.
Returns: a BatchBuilder for adding items, configuring workflows, and submitting.
List<Long> orderIds = orderRepository.findPending();
scheduler.enqueueBatch("Process Pending Orders")
.forEach(orderIds, orderId -> orderService.process(orderId))
.onProgress(ctx -> log.info("Batch {} is {}% complete", ctx.batchId(), ctx.percentDone()))
.thenOnBatchSuccess(() -> notificationService.sendBatchComplete())
.thenOnBatchFailure(() -> alertService.sendBatchFailed())
.submit();streamingBatch
<T extends Serializable> StreamingBatchBuilder<T> streamingBatch(String name)Creates a StreamingBatchBuilder for memory-efficient processing of large datasets. Items are read from a stream and inserted in chunks, avoiding loading the entire dataset into memory.
Type Parameters:
T-- the type of items to process; must implementSerializable.
Parameters:
name-- human-readable name for the batch.
Returns: a StreamingBatchBuilder for configuring the stream source, processing action, chunk size, and callbacks.
scheduler.<Long>streamingBatch("Migrate Users")
.fromStream(userRepository.streamAllUserIds())
.process(userId -> migrationService.migrateUser(userId))
.withChunkSize(1000)
.onProgress(ctx -> log.info("Streamed {} items in {} chunks",
ctx.processedItems(), ctx.chunksInserted()))
.thenOnBatchSuccess(() -> log.info("Migration complete"))
.start();Job replacement
replace
JobHandle replace(UUID jobId, Duration delay,
SerializableCheckedRunnable newTask, JobOptions opts)Replaces an existing job with a new one. The original job is canceled and a new job is created with the specified delay and options.
Parameters:
jobId-- the ID of the existing job to replace.delay-- delay before the replacement job becomes eligible for execution.newTask-- the new task to execute.opts-- execution options for the replacement job.
Returns: a JobHandle for the newly created replacement job.
// Replace a pending job with updated logic
JobHandle replacement = scheduler.replace(
originalHandle.id(),
Duration.ofMinutes(5),
() -> updatedProcessingService.process(data),
JobOptions.defaults().withPriority(JobPriority.HIGH));Job control methods
cancelJob
boolean cancelJob(UUID jobId)Cancels a job by its ID.
- PENDING jobs transition directly to CANCELED.
- RUNNING jobs transition to CANCELED; the executor should check status before committing results.
- Jobs in terminal states (SUCCEEDED, FAILED, CANCELED) cannot be canceled.
Parameters:
jobId-- the ID of the job to cancel.
Returns: true if the job was successfully canceled; false if the job was not found or is already in a terminal state.
boolean canceled = scheduler.cancelJob(handle.id());
if (!canceled) {
log.warn("Job {} could not be canceled", handle.id());
}pauseJob
boolean pauseJob(UUID jobId)Pauses a job, preventing it from being picked up for execution.
- Only PENDING jobs can be paused.
- The job's previous status is recorded so it can be restored on resume.
- Jobs in RUNNING, WAITING, or terminal states cannot be paused.
- Idempotent: pausing an already-PAUSED job returns
truewithout error.
Parameters:
jobId-- the ID of the job to pause.
Returns: true if the job was paused or was already paused; false if the job was not found or in an incompatible state.
scheduler.pauseJob(jobId);
// Later...
scheduler.resumeJob(jobId);resumeJob
boolean resumeJob(UUID jobId)Resumes a paused job, making it eligible for execution again.
- The job returns to the status it had before being paused.
- Resuming a previously PENDING job makes it eligible for polling again.
- Idempotent: resuming a non-paused job returns
falsewithout error.
Parameters:
jobId-- the ID of the job to resume.
Returns: true if the job was resumed; false if the job was not found or not in PAUSED state.
boolean resumed = scheduler.resumeJob(jobId);retryJob
boolean retryJob(UUID jobId)Retries a failed job by resetting it to PENDING status. This is the primary mechanism for manual retry of jobs in the Dead Letter Queue.
- The attempt counter is reset to 0.
- Error information is cleared.
- Scheduled time is set to now, making the job immediately eligible for execution.
- Only FAILED jobs can be retried.
Parameters:
jobId-- the ID of the failed job to retry.
Returns: true if the job was successfully reset to PENDING; false if not found or not FAILED.
// Manual DLQ retry
boolean retried = scheduler.retryJob(failedJobId);
if (retried) {
log.info("Job {} re-queued for execution", failedJobId);
}retryJobs
int retryJobs(JobFilter filter, int limit)Recovers a bounded set of failed jobs in one atomic store operation. Ratchet intersects the filter with FAILED, ignores archived rows, resets retry metadata, and makes the selected jobs immediately eligible again. The limit must be from 1 through 1000; repeat the call to drain a larger incident in bounded transactions.
JobFilter billingFailures = JobFilter.builder()
.tags("billing")
.createdAfter(incidentStarted)
.sortField(JobQuerySortField.CREATED_AT)
.sortAscending(true)
.build();
int recovered = scheduler.retryJobs(billingFailures, 250);- All
JobFiltercriteria and ordering apply, but only currentFAILEDjobs are eligible. - The batch commits or rolls back as a unit. A business-key reservation conflict rolls back the whole selected batch instead of partially recovering it.
- This administrative operation does not run per-job authorization checks. Use
retryJob(UUID)when each job needs an authorization decision. - One
JobsBulkRetriedEventand one scheduler wakeup are emitted when the count is positive. Ratchet does not emit oneJobRetryingEventper recovered job.
Signal delivery methods
Signal-waiting jobs are created through JobBuilder.awaitSignal(). They remain in WAITING status until a matching signal is delivered, or until their signal timeout fails the job.
deliverSignal by job ID
int deliverSignal(UUID jobId, Serializable payload)
int deliverSignal(UUID jobId, SignalDecision decision)Delivers a signal to a specific WAITING job. The job transitions to PENDING, and the payload is made available through JobContext.signalPayload().
Returns: 1 when a waiting job was unblocked, 0 when the job was not found, was not waiting, or signal support is not configured.
JobHandle approval = scheduler.enqueue(() -> approvalJob.continueOrder(orderId))
.awaitSignal("order:" + orderId + ":approved", Duration.ofHours(24))
.submit();
int delivered = scheduler.deliverSignal(
approval.id(),
SignalDecision.approved("approved-by-manager"));deliverSignal by signal key
int deliverSignal(String signalKey, Serializable payload)
int deliverSignal(String signalKey, SignalDecision decision)Delivers a signal to every WAITING job with the matching signal key. Store implementations perform this as an atomic bulk update so already-unblocked jobs are not counted twice.
int unblocked = scheduler.deliverSignal(
"billing-cycle-2026-05-ready",
SignalDecision.approved("ledger-closed"));
log.info("Released {} waiting billing jobs", unblocked);SignalDecision is scheduler-visible metadata for audit, metrics, and events. A rejected decision still unblocks the job; the job code decides how to handle it:
SignalDecision decision =
JobContext.current().signalPayload(SignalDecision.class);
if (decision != null && decision.isRejected()) {
throw new RejectedOrderException(decision.rejectionReason());
}Recurring job management
cancelRecurringJobsByTag
int cancelRecurringJobsByTag(String tag)Cancels all recurring jobs associated with the specified tag.
Parameters:
tag-- the tag identifying the recurring jobs to cancel.
Returns: the number of jobs canceled.
int canceled = scheduler.cancelRecurringJobsByTag("maintenance");
log.info("Canceled {} recurring maintenance jobs", canceled);cancelRecurringJobByBusinessKey
int cancelRecurringJobByBusinessKey(String businessKey)Cancels the active recurring job with the specified business key. This is the primary mechanism for replacing a recurring job definition during redeployment. Only jobs in active states (PENDING, RUNNING, PAUSED) with a matching business key and recurring job type are affected.
Parameters:
businessKey-- the business key identifying the recurring job to cancel.
Returns: the number of jobs canceled (0 or 1, since business keys are active-unique).
// Replace a recurring job by canceling the old one first
scheduler.cancelRecurringJobByBusinessKey("daily-report");
scheduler.scheduleRecurring("0 0 10 * * ?", ZoneId.of("UTC"),
() -> reportService.generateDailyReport())
.withBusinessKey("daily-report")
.submit();Event listener management
addEventListener
void addEventListener(Consumer<Object> listener)Registers a programmatic event listener that receives all scheduler events. For type-safe event observation, use CDI @Observes with specific event types instead. This method is intended for non-CDI contexts or when receiving all events is desired.
Parameters:
listener-- a consumer that receives all scheduler events. Events are instances of classes in therun.ratchet.api.eventpackage.
scheduler.addEventListener(event -> {
if (event instanceof JobFailedEvent failed) {
metrics.counter("jobs.failed").increment();
log.error("Job {} failed: {}", failed.getJobId(), failed.getErrorMessage());
} else if (event instanceof JobCompletedEvent completed) {
metrics.timer("jobs.duration")
.record(completed.getExecutionTimeMs(), TimeUnit.MILLISECONDS);
}
});removeEventListener
void removeEventListener(Consumer<Object> listener)Removes a previously registered event listener.
Parameters:
listener-- the listener to remove (must be the same instance passed toaddEventListener).
Consumer<Object> listener = event -> { /* ... */ };
scheduler.addEventListener(listener);
// Later...
scheduler.removeEventListener(listener);JobHandle
JobHandle is the return type of all submission methods. It is a @FunctionalInterface with a single method:
@FunctionalInterface
public interface JobHandle {
UUID id();
}The ID is a java.util.UUID UUIDv7 value, globally unique within the scheduler, and remains valid throughout the job's lifecycle.
JobHandle handle = scheduler.enqueue(() -> processData()).submit();
UUID jobId = handle.id();
// Use the ID for lifecycle operations
scheduler.cancelJob(jobId);
scheduler.pauseJob(jobId);
scheduler.retryJob(jobId);RecurringJobBuilder
Returned by scheduleRecurring(). The configuration methods documented below set options, tags, and the business key:
withOptions
RecurringJobBuilder withOptions(JobOptions options)Configures execution options (retry, timeout, priority, backoff) for the recurring job.
withTags
RecurringJobBuilder withTags(List<String> tags)Associates tags with the job for filtering and categorization.
withBusinessKey
RecurringJobBuilder withBusinessKey(String key)Sets the business key for active-unique identity. While the job is active (PENDING, RUNNING, PAUSED), no other job may share the same business key.
After trimming, a business key may contain up to 255 printable ASCII characters (U+0020 through U+007E). Ratchet rejects longer or non-ASCII keys instead of leaving a store to truncate or convert them.
submit
JobHandle submit()Finalizes configuration and submits the recurring job to the scheduler.