Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/msolli/proletarian/llms.txt

Use this file to discover all available pages before exploring further.

A job handler is the function you provide as the second argument to worker/create-queue-worker. Proletarian calls it each time a job is dequeued and ready to be processed. The handler is responsible for performing the actual work — sending an email, calling an external API, updating a search index, and so on. How you implement it is entirely up to you; Proletarian places no constraints on the function beyond its calling convention.

Default Mode: (job-type, payload)

By default, the handler is called with two arguments:
ArgumentDescription
job-typeThe keyword passed as the second argument to job/enqueue!
payloadThe data map passed as the third argument to job/enqueue!
(defn handle-job! [job-type payload]
  ;; dispatch on job-type and perform work
  )

(def my-worker
  (worker/create-queue-worker data-source handle-job!))

Dispatching Multiple Job Types with a Multimethod

A defmulti dispatched on job-type is the idiomatic way to route jobs to the correct implementation:
(ns your-app.handlers
  (:require [proletarian.worker :as worker]))

;; Define the multimethod — dispatch on the first argument (job-type)
(defmulti handle-job!
  (fn [job-type _payload] job-type))

;; Handle confirmation emails
(defmethod handle-job! ::confirmation-email
  [_job-type {:keys [email-address first-name]}]
  (email/send! {:to      email-address
                :subject "Please confirm your email"
                :body    (render-template :confirmation {:name first-name})}))

;; Handle search index updates
(defmethod handle-job! ::reindex-document
  [_job-type {:keys [document-id]}]
  (search/index! (db/fetch-document document-id)))

;; Catch-all for unknown job types
(defmethod handle-job! :default
  [job-type payload]
  (log/error "Unknown job type" {:job-type job-type :payload payload}))
Pass the multimethod directly to create-queue-worker:
(def worker
  (worker/create-queue-worker data-source handle-job!))

Advanced Mode: Full Job Map

When you need access to job metadata — the job ID, the number of prior attempts, or the time the job was enqueued — set :proletarian/handler-fn-mode to :advanced. In this mode, the handler is called with a single argument: a map containing all of the job’s attributes.
(worker/create-queue-worker
  data-source
  handle-job!
  {:proletarian/handler-fn-mode :advanced})
The job map contains the following keys:
KeyDescription
:proletarian.job/job-typeKeyword identifying the job type
:proletarian.job/payloadThe job’s payload data
:proletarian.job/job-idUnique UUID for this job
:proletarian.job/queueThe queue the job was enqueued on
:proletarian.job/enqueued-atjava.time.Instant when the job was created
:proletarian.job/process-atjava.time.Instant the job was scheduled for
:proletarian.job/attemptsNumber of attempts so far (1 on the first attempt)
(defmulti handle-job!
  (fn [{:proletarian.job/keys [job-type]}] job-type))

(defmethod handle-job! ::send-invoice
  [{:proletarian.job/keys [job-id payload attempts]}]
  (log/info "Processing invoice job"
            {:job-id   job-id
             :attempts attempts
             :invoice  (:invoice-id payload)})
  (billing/send-invoice! (:invoice-id payload)))

Closing Over System State

Your handler function frequently needs access to shared resources — database connections, HTTP clients, configuration values. The cleanest pattern is to close over those dependencies when the handler is defined, rather than using global state:
(defn make-handler [system]
  (fn [job-type payload]
    (case job-type
      ::send-email
      (email/send! (:mailer system) payload)

      ::update-search-index
      (search/index! (:search-client system) payload)

      (throw (ex-info "Unknown job type" {:job-type job-type})))))

;; At system startup:
(def worker
  (worker/create-queue-worker
    (:db system)
    (make-handler system)))
This approach works naturally with component libraries like Component, Integrant, or Mount.

Exception Handling

Proletarian catches java.lang.Exception and its subclasses thrown by the handler. When caught, the retry strategy is invoked and the job is either rescheduled or moved to the archive with a :failure status. Other Throwable subclasses — such as java.lang.Error and its descendants — are not caught by Proletarian’s retry logic. They propagate up the call stack and will cause the worker thread to log a ::job-worker-error event and, by default, stop the worker.
Your handler must be idempotent. Proletarian provides an at-least-once delivery guarantee, which means a job may be executed more than once in rare failure scenarios (for example, if the database goes offline mid-transaction after the handler succeeds but before the job is archived). Always design handlers so that running them multiple times with the same payload is safe. See At-Least-Once Processing for strategies.

Build docs developers (and LLMs) love