Skip to content

Event System Reference

Observing and reacting to job lifecycle events. Ratchet publishes events at every state transition. Use them for monitoring, alerting, and custom integrations.

Listening to events

CDI observers

Use CDI @Observes for type-safe event handling:

java
@ApplicationScoped
public class JobMonitor {

    public void onJobStarted(@Observes JobStartedEvent event) {
        log.info("Job {} started on node {}", event.getJobId(), event.getNodeId());
    }

    public void onJobFailed(@Observes JobFailedEvent event) {
        log.error("Job {} failed (attempt {}): {}",
            event.getJobId(), event.getRetryAttempt(), event.getErrorMessage());
    }

    public void onJobCompleted(@Observes JobCompletedEvent event) {
        log.info("Job {} completed in {} ms",
            event.getJobId(), event.getExecutionTimeMs());
    }
}

Programmatic listeners

Register listeners via JobSchedulerService.addEventListener():

java
scheduler.addEventListener(event -> {
    if (event instanceof JobFailedEvent failed) {
        metrics.counter("jobs.failed").increment();
    } else if (event instanceof JobCompletedEvent completed) {
        metrics.timer("jobs.duration")
            .record(completed.getExecutionTimeMs(), TimeUnit.MILLISECONDS);
    }
});

Event base class

All job lifecycle events extend AbstractJobSchedulerEvent:

java
public abstract class AbstractJobSchedulerEvent implements Serializable {
    public UUID getJobId()
    public String getBusinessKey()
    public JobType getJobType()
    public JobPriority getPriority()
    public String getNodeId()
    public Instant getTimestamp()
}
MethodReturn TypeDescription
getJobId()UUIDUUIDv7 database ID of the job that triggered this event
getBusinessKey()StringHuman-readable business key (may be null)
getJobType()JobTypeJob category: SINGLE, BATCH, CHAIN, WORKFLOW, RECURRING, SYSTEM
getPriority()JobPriorityPriority level of the job
getNodeId()StringIdentifier of the cluster node that processed this job
getTimestamp()InstantWhen this event was created

Job lifecycle events

JobStartedEvent

Fired when a job begins execution.

java
public class JobStartedEvent extends AbstractJobSchedulerEvent

No additional fields beyond the base class.

java
public void onStarted(@Observes JobStartedEvent event) {
    log.info("[{}] Job {} ({}) started on node {}",
        event.getTimestamp(), event.getJobId(),
        event.getJobType(), event.getNodeId());
}

JobCompletedEvent

Fired when a job completes successfully.

java
public class JobCompletedEvent extends AbstractJobSchedulerEvent {
    public Long getExecutionTimeMs()
}
MethodReturn TypeDescription
getExecutionTimeMs()LongExecution duration in milliseconds (may be null)
java
public void onCompleted(@Observes JobCompletedEvent event) {
    metrics.timer("job.duration").record(
        event.getExecutionTimeMs(), TimeUnit.MILLISECONDS);
}

JobFailedEvent

Fired when a job reaches terminal FAILED state. This includes execution failures that exhaust retry handling and terminal timeout transitions; retryable per-attempt failures do not publish it.

java
public class JobFailedEvent extends AbstractJobSchedulerEvent {
    public String getErrorMessage()
    public int getRetryAttempt()
}
MethodReturn TypeDescription
getErrorMessage()StringError message from the failure
getRetryAttempt()intFinal retry attempt number
java
public void onFailed(@Observes JobFailedEvent event) {
    log.error("Job {} failed on attempt {}: {}",
        event.getJobId(), event.getRetryAttempt(), event.getErrorMessage());
}

JobRetryingEvent

Fired when a job is being retried after a failure.

java
public class JobRetryingEvent extends AbstractJobSchedulerEvent {
    public String getErrorMessage()
    public int getRetryAttempt()
    public Instant getScheduledTime()
}
MethodReturn TypeDescription
getErrorMessage()StringError from the failure that triggered the retry
getRetryAttempt()intCurrent retry attempt number
getScheduledTime()InstantWhen the retry is scheduled (after backoff)
java
public void onRetrying(@Observes JobRetryingEvent event) {
    log.warn("Job {} retrying (attempt {}), next run at {}",
        event.getJobId(), event.getRetryAttempt(), event.getScheduledTime());
}

JobExecutionTimedOutEvent

Fired after a RUNNING job exceeds its configured execution timeout and Ratchet successfully applies the resulting retry or terminal-failure transition. It is not fired for the soft warning threshold or when another path wins the transition race.

java
public class JobExecutionTimedOutEvent extends AbstractJobSchedulerEvent {
    public Duration getExecutionTimeout()
    public Duration getElapsedTime()
    public int getRetryAttempt()
}
MethodReturn TypeDescription
getExecutionTimeout()DurationConfigured maximum execution duration that was exceeded
getElapsedTime()DurationObserved execution duration when the watchdog fired
getRetryAttempt()int1-based failed-attempt count recorded by the transition

A retrying timeout also publishes JobRetryingEvent. A terminal timeout also publishes JobFailedEvent and follows the normal dead-letter path. Observe this event when the application needs timeout-specific handling without parsing a generic failure message:

java
public void onExecutionTimeout(@Observes JobExecutionTimedOutEvent event) {
    timeoutAlertExecutor.execute(() -> alertService.notifyTimeout(event));
}

Event delivery is synchronous, so observers must offload network calls and other blocking work. Ratchet does not select an alert channel; that remains application configuration.

JobCancelledEvent

Fired when a job cancellation is confirmed.

java
public class JobCancelledEvent extends AbstractJobCancellationEvent
java
public String getPreviousStatus()
public Long getExecutionTimeMs()
MethodReturn TypeDescription
getPreviousStatus()StringStatus before cancellation
getExecutionTimeMs()LongExecution duration in milliseconds when known
java
public void onCancelled(@Observes JobCancelledEvent event) {
    log.info("Job {} canceled", event.getJobId());
}

JobsBulkCancelledEvent

Fired exactly once per successful bulk cancel-by-tag operation, when at least one job was cancelled. Produced by cancelJobsByTag() and cancelRecurringJobsByTag().

Bulk cancellation does not carry a single job id, business key, or priority, so this event does not extend AbstractJobSchedulerEvent.

java
public class JobsBulkCancelledEvent implements Serializable {
    public String getTag()
    public int getCount()
    public Instant getCancelledAt()
}
MethodReturn TypeDescription
getTag()StringTag used to select jobs for cancellation
getCount()intNumber of jobs successfully cancelled
getCancelledAt()InstantWhen the bulk operation completed
java
public void onBulkCancelled(@Observes JobsBulkCancelledEvent event) {
    log.info("Cancelled {} jobs tagged {}", event.getCount(), event.getTag());
}

JobsBulkRetriedEvent

Fired exactly once when retryJobs(JobFilter, int) resets at least one failed job. Bulk retry does not publish a JobRetryingEvent for each selected job.

java
public class JobsBulkRetriedEvent implements Serializable {
    public JobFilter getFilter()
    public int getLimit()
    public int getCount()
    public Instant getRetriedAt()
}
MethodReturn TypeDescription
getFilter()JobFilterSelection requested by the caller
getLimit()intMaximum jobs allowed in the recovery batch
getCount()intNumber of failed jobs reset to pending
getRetriedAt()InstantWhen the bulk operation completed
java
public void onBulkRetried(@Observes JobsBulkRetriedEvent event) {
    log.info("Recovered {} failed jobs", event.getCount());
}

JobPausedEvent

Fired when a job is paused.

java
public class JobPausedEvent extends AbstractJobSchedulerEvent

No additional fields.

JobResumedEvent

Fired when a paused job is resumed.

java
public class JobResumedEvent extends AbstractJobSchedulerEvent

No additional fields.

JobDlqEvent

Fired when a job enters terminal dead-letter handling. This can follow retry exhaustion, a non-retryable failure such as poison data, or a protective runtime limit such as the retry-buffer hard cap.

java
public class JobDlqEvent extends AbstractJobSchedulerEvent {
    public String getErrorMessage()
    public int getRetryAttempt()
}
MethodReturn TypeDescription
getErrorMessage()StringError from the final failure
getRetryAttempt()intFinal recorded retry count before DLQ; zero means no retry was consumed
java
public void onDlq(@Observes JobDlqEvent event) {
    alertService.sendDlqAlert(event.getJobId(), event.getErrorMessage());
    metrics.counter("jobs.dlq").increment();
}

JobCallbackFailedEvent

Fired when a lifecycle callback (onSuccess / onFailure) throws an exception. The callback failure does not affect the job's recorded outcome.

java
public class JobCallbackFailedEvent extends AbstractJobSchedulerEvent {
    public CallbackType getCallbackType()
    public String getErrorMessage()
    public String getCauseClassName()
    public int getCallbackAttempt()
}
MethodReturn TypeDescription
getCallbackType()CallbackTypeWhich callback failed: ON_SUCCESS or ON_FAILURE
getErrorMessage()StringMessage from the thrown callback exception (may be null)
getCauseClassName()StringClass name of the thrown callback exception
getCallbackAttempt()int1-based callback invocation attempt
java
public void onCallbackFailed(@Observes JobCallbackFailedEvent event) {
    log.warn("Job {} {} callback failed: {} ({})",
        event.getJobId(), event.getCallbackType(),
        event.getErrorMessage(), event.getCauseClassName());
}

Batch events

BatchCompletingEvent

Fired when a batch is finishing (the last child job is completing).

java
public class BatchCompletingEvent extends AbstractJobSchedulerEvent
java
public int getTotalItems()
public int getCompletedItems()
public int getFailedItems()
MethodReturn TypeDescription
getTotalItems()intTotal child jobs in the batch
getCompletedItems()intSuccessfully completed child jobs so far
getFailedItems()intFailed child jobs so far

BatchCompletedEvent

Fired when a batch is fully complete (all child jobs have finished).

java
public class BatchCompletedEvent extends AbstractJobSchedulerEvent {
    public int getTotalItems()
    public int getCompletedItems()
    public int getFailedItems()
}
MethodReturn TypeDescription
getTotalItems()intTotal child jobs in the batch
getCompletedItems()intSuccessfully completed child jobs
getFailedItems()intFailed child jobs
java
public void onBatchCompleted(@Observes BatchCompletedEvent event) {
    double successRate = event.getTotalItems() > 0
        ? (double) event.getCompletedItems() / event.getTotalItems()
        : 1.0;

    log.info("Batch {} complete: {}/{} succeeded, {} failed",
        event.getJobId(), event.getCompletedItems(),
        event.getTotalItems(), event.getFailedItems());

    if (event.getFailedItems() > 0) {
        alertService.batchPartialFailure(event.getJobId(), event.getFailedItems());
    }
}

BatchChunkFailureEvent

Fired when a streaming-batch chunk fails to persist (the chunk's bulk insert threw) on the invocation-mode submission path (InvocationStreamingBatchBuilder). Lambda-mode streaming batches do not emit it.

java
@Incubating
public class BatchChunkFailureEvent extends AbstractJobSchedulerEvent {
    public int getChunkIndex()
    public int getChunkSize()
    public String getFailureReason()
}
MethodReturn TypeDescription
getChunkIndex()intZero-based index of the chunk that failed
getChunkSize()intNumber of items in the failed chunk
getFailureReason()StringFailure description from the underlying store exception

This is a best-effort, pre-rollback diagnostic. Streaming submission runs in one transaction, so a chunk failure rolls back the batch parent and every previously inserted chunk with it; the event fires before that rollback and may reference a batch parent id (getJobId()) that never commits. Treat it as an operational signal of the failed submission, not as a pointer to durable rows.

Chain event types

ChainStartedEvent

Fired when a workflow chain begins execution.

java
public class ChainStartedEvent extends AbstractJobSchedulerEvent {
    public UUID getParentJobId()
}
MethodReturn TypeDescription
getParentJobId()UUIDUUIDv7 ID of the parent job that owns this chain

ChainCompletedEvent

Fired when a workflow chain succeeds.

java
public class ChainCompletedEvent extends AbstractJobSchedulerEvent {
    public UUID getParentJobId()
}

ChainFailedEvent

Fired when a workflow chain fails.

java
public class ChainFailedEvent extends AbstractJobSchedulerEvent {
    public UUID getParentJobId()
    public String getErrorMessage()
}
java
public void onChainFailed(@Observes ChainFailedEvent event) {
    log.error("Chain for parent job {} failed: {}",
        event.getParentJobId(), event.getErrorMessage());
}

Workflow events

WorkflowBranchTriggeredEvent

Fired when a workflow condition matches and a branch is triggered.

java
public class WorkflowBranchTriggeredEvent extends AbstractJobSchedulerEvent
java
public String getBranchCondition()
public UUID getNextJobId()
MethodReturn TypeDescription
getBranchCondition()StringDescription of the branch condition that matched
getNextJobId()UUIDChild job ID scheduled for the branch

Signal events

These events accompany the signal-waiting job lifecycle (WAITING status and deliverSignal()).

JobSignalWaitingEvent

Fired when a job has been created in WAITING state, blocked on a named signal.

java
public class JobSignalWaitingEvent extends AbstractJobSchedulerEvent {
    public String getSignalKey()
    public Duration getSignalTimeout()
}
MethodReturn TypeDescription
getSignalKey()StringSignal key the job is waiting on
getSignalTimeout()DurationMaximum wait duration, or null for no timeout

JobSignaledEvent

Fired after a signal is successfully delivered to a WAITING job, transitioning it to PENDING. Published only on a successful delivery; a delivery that finds the job already terminal or non-WAITING produces no event.

java
public class JobSignaledEvent extends AbstractJobSchedulerEvent {
    public String getSignalKey()
    public String getSignalDeliveredBy()
    public SignalDecision.Outcome getOutcome()
    public String getRejectionReason()
}
MethodReturn TypeDescription
getSignalKey()StringSignal key delivered to the waiting job
getSignalDeliveredBy()StringPrincipal or component that delivered the signal
getOutcome()SignalDecision.OutcomeApproval/rejection outcome
getRejectionReason()StringRejection reason, or null when approved

JobsBulkSignaledEvent

Fired exactly once per successful key-based signal delivery when at least one WAITING job is unblocked. Like JobsBulkCancelledEvent, a key-based delivery can unblock many jobs, so this event does not extend AbstractJobSchedulerEvent.

java
public class JobsBulkSignaledEvent implements Serializable {
    public String getSignalKey()
    public int getCount()
    public String getSignalDeliveredBy()
    public SignalDecision.Outcome getOutcome()
    public String getRejectionReason()
    public Instant getSignaledAt()
}
MethodReturn TypeDescription
getSignalKey()StringSignal key that was delivered
getCount()intNumber of WAITING jobs unblocked
getSignalDeliveredBy()StringPrincipal or component that delivered the signal
getOutcome()SignalDecision.OutcomeApproval/rejection outcome
getRejectionReason()StringRejection reason, or null when approved
getSignaledAt()InstantWhen the signal was delivered

JobSignalTimedOutEvent

Fired when a WAITING job's signal timeout elapses and it is transitioned to FAILED. The same terminal transition publishes JobFailedEvent immediately after this timeout-specific event.

java
public class JobSignalTimedOutEvent extends AbstractJobSchedulerEvent {
    public String getSignalKey()
    public Duration getSignalTimeout()
}
MethodReturn TypeDescription
getSignalKey()StringSignal key the job was waiting on
getSignalTimeout()DurationConfigured maximum wait duration that elapsed (never null)

System metrics

Ratchet does not publish aggregate system-metrics through the event listener API. For per-job metrics (start, completion, failure, retry timings), implement the MetricsCollector SPI and wire it to your monitoring backend (Micrometer, StatsD, Prometheus, etc.).

Example: comprehensive monitoring

java
@ApplicationScoped
public class SchedulerMonitoring {

    @Inject Logger log;
    @Inject MeterRegistry metrics;
    @Inject AlertingService alerts;

    public void onStarted(@Observes JobStartedEvent e) {
        metrics.counter("jobs.started",
            "type", e.getJobType().name(),
            "priority", e.getPriority().name()).increment();
    }

    public void onCompleted(@Observes JobCompletedEvent e) {
        metrics.counter("jobs.completed", "type", e.getJobType().name()).increment();
        if (e.getExecutionTimeMs() != null) {
            metrics.timer("jobs.duration", "type", e.getJobType().name())
                .record(e.getExecutionTimeMs(), TimeUnit.MILLISECONDS);
        }
    }

    public void onFailed(@Observes JobFailedEvent e) {
        metrics.counter("jobs.failed", "type", e.getJobType().name()).increment();
    }

    public void onExecutionTimeout(@Observes JobExecutionTimedOutEvent e) {
        metrics.counter("jobs.execution.timeout", "type", e.getJobType().name()).increment();
    }

    public void onDlq(@Observes JobDlqEvent e) {
        metrics.counter("jobs.dlq", "type", e.getJobType().name()).increment();
        alerts.notify("Job " + e.getJobId() + " moved to DLQ: " + e.getErrorMessage());
    }

    public void onBatchCompleted(@Observes BatchCompletedEvent e) {
        metrics.gauge("batch.success_rate",
            (double) e.getCompletedItems() / Math.max(e.getTotalItems(), 1));
    }

    public void onBulkCancelled(@Observes JobsBulkCancelledEvent e) {
        metrics.counter("jobs.cancelled.bulk", "tag", e.getTag())
            .increment(e.getCount());
    }

    public void onBulkRetried(@Observes JobsBulkRetriedEvent e) {
        metrics.counter("jobs.retried.bulk").increment(e.getCount());
    }
}

See also