Jobs & dispatch
A job is a class (or lambda) with a handle() method. Mark it ShouldQueue so Bus pushes it instead of running it inline.
Job contracts
Section titled “Job contracts”Optional JobContract methods:
tries()— attempts before the job is marked failed (default3)backoffSeconds()— delay before a retry (default0)displayName()— label in logs and failed-job lists
Prefer Jackson-serializable jobs (public no-arg constructor + readable properties). Each payload stores a jobBody JSON snapshot so another process can restore the job without an in-memory factory.
Dispatch
Section titled “Dispatch”val bus = app.resolve(Bus::class.java)
bus.dispatch(SendInvoice())bus.dispatch(SendInvoice(), connection = "redis", queue = "mail")bus.later(delaySeconds = 60, job = SendInvoice())bus.dispatchSync(SendInvoice()) // always inlinebus.dispatch(new SendInvoice(), "redis", "mail", 0L);bus.later(60L, new SendInvoice());later / delaySeconds sets availableAt on the payload. Array, Redis, and database drivers skip the job until the delay elapses. The sync driver always runs immediately.
Chains
Section titled “Chains”Run jobs one after another. The next job is dispatched only after the previous handle() succeeds.
bus.chain(ReserveStock(), ChargeCard(), SendReceipt()).dispatch()Workers bind a WorkerContext so the chain can continue across pops. Keep chain steps serializable if workers run in other processes.
Batches
Section titled “Batches”Dispatch a set of jobs, then run callbacks when the set finishes. Workers record success/failure against the batch as each job completes.
val batch = bus.batch(JobA(), JobB(), JobC()) .name("fulfill-order") .allowFailures() // optional: finish even if some jobs fail .then { b -> /* all done (or allowed failures) */ } .catch { b, err -> /* cancelled or hard failure */ } .finally { b -> /* always */ } .dispatch()
batch.idbatch.size()batch.progress() // 0.0 … 100.0batch.pendingJobs()batch.failedJobIds()batch.cancel() // stop tracking; runs catch/finallyBatch batch = bus.batch(new JobA(), new JobB()) .name("fulfill-order") .allowFailures() .then(b -> { }) .onFinally(b -> { }) .dispatch();Batch repository
Section titled “Batch repository”By default batches live in memory (InMemoryBatchRepository). For durable counts across workers, set:
queue.batch.driver=databasequeue.batch.table=job_batchesQueueServiceProvider creates a job_batches table (Cadence Database required) and wires DatabaseBatchRepository. Callbacks (then / catch / finally) stay in-process on the dispatching JVM; durable rows track totals, pending, failures, and finished/cancelled flags.
Queue events
Section titled “Queue events”Optional process-wide hooks via QueueEvents.setDispatcher:
| Event | When |
|---|---|
BatchDispatched |
Batch stored and jobs pushed |
BatchJobSucceeded / BatchJobFailed |
Per-job outcome |
BatchFinished |
Batch completed or cancelled |
JobQueued / JobProcessed / JobFailed |
Individual job lifecycle |
Middleware
Section titled “Middleware”Implement HasMiddleware and return JobMiddleware wrappers. Middleware runs outermost-first around handle().
class NotifyUsers : Job, ShouldQueue, HasMiddleware { override fun middleware() = listOf(RateLimited(maxAttempts = 10, decaySeconds = 60, key = "notify")) override fun handle() { /* … */ }}Throw ReleaseJobException(delaySeconds) to put the job back on the queue without counting a failure (used by RateLimited when over limit).
Unique jobs
Section titled “Unique jobs”class RebuildSearchIndex : Job, ShouldQueue, ShouldBeUnique { override fun uniqueId() = "search-index" override fun uniqueForSeconds() = 3600L override fun handle() { /* … */ }}If the unique lock is held, the worker re-queues the payload and moves on.