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.

When a job handler throws an exception, Proletarian doesn’t immediately discard the job. Instead, it calls your retry strategy function to determine how — and whether — the job should be rescheduled. This lets you configure fine-grained retry behavior per job type, per exception type, or based on information embedded in the exception itself.

What Triggers Retry Logic

Proletarian only invokes the retry strategy for java.lang.Exception and its subclasses. Other Throwable subtypes — most notably java.lang.Error (e.g. OutOfMemoryError, StackOverflowError) — are not caught by the retry machinery. They propagate up the call stack and cause the worker thread to log a ::job-worker-error event, which by default stops the worker entirely.

The Retry Strategy Function

Set the :proletarian/retry-strategy-fn option on create-queue-worker to provide a retry strategy. The function receives two arguments:
  1. job — a map with the job’s attributes (same keys as advanced handler mode)
  2. exception — the java.lang.Exception that was thrown
It must return either a retry strategy map or nil (meaning no retry).
(defn my-retry-strategy [job exception]
  {:retries 3
   :delays  [1000 5000 30000]})

The Retry Strategy Map

KeyTypeDescription
:retriesinteger ≥ 0Maximum number of retries. Total attempts = :retries + 1.
:delaysvector of integersMilliseconds to wait before each retry. If fewer delays are provided than retries, the last value is repeated for all remaining retries.

Examples

;; 2 retries: wait 1 s, then 5 s
{:retries 2
 :delays  [1000 5000]}

;; 4 retries: wait 2 s, then 10 s, 10 s, 10 s
;; (last delay of 10 000 ms is repeated for retries 2-4)
{:retries 4
 :delays  [2000 10000]}

;; No retries — give up immediately
nil
Returning nil is the default behavior. If you do not supply :proletarian/retry-strategy-fn, Proletarian uses (constantly nil), which means every failing job is immediately moved to the archive as a failure.
A retry delay is a minimum wait time, not a precise schedule. The actual retry time may be later than specified, depending on the configured :proletarian/polling-interval-ms and how many jobs are ahead of this one in the queue.

Configuring Retry Strategy on the Worker

(require '[proletarian.worker :as worker])

(defn retry-strategy [job exception]
  ;; Retry all jobs up to 4 times with increasing delays
  {:retries 4
   :delays  [1000 5000 30000 60000]})

(def my-worker
  (worker/create-queue-worker
    data-source
    handle-job!
    {:proletarian/retry-strategy-fn retry-strategy}))
You can also vary the strategy based on the job type or exception:
(defn retry-strategy [job exception]
  (case (:proletarian.job/job-type job)
    ::send-email     {:retries 5  :delays [5000 30000 60000]}
    ::call-webhook   {:retries 3  :delays [2000 10000]}
    ;; Default: no retries for unknown job types
    nil))
If you call an external HTTP API, the response may include a Retry-After header telling you exactly when to retry. Encode this value into the exception using ex-info, then read it back in your retry strategy:
;; In your HTTP client code:
(throw (ex-info "Rate limited" {:retry-after-ms 45000}))

;; In your retry strategy:
(defn retry-strategy [job exception]
  (let [retry-ms (or (:retry-after-ms (ex-data exception)) 10000)]
    {:retries 3
     :delays  [retry-ms]}))

Handling Permanently Failed Jobs

When a job exhausts all of its retries, Proletarian:
  1. Moves the job to the archived_job table with a :failure status.
  2. Calls the function you provided as :proletarian/failed-job-fn.

The Failed-Job Function

The function receives the same two arguments as the retry strategy function:
  1. job — the full job map (:proletarian.job/job-type, :proletarian.job/payload, :proletarian.job/job-id, :proletarian.job/queue, :proletarian.job/enqueued-at, :proletarian.job/process-at, :proletarian.job/attempts)
  2. exception — the exception from the final failing attempt
(defn on-failed-job [job exception]
  (let [{:proletarian.job/keys [job-id job-type payload attempts]} job]
    ;; Log at ERROR so your monitoring picks it up
    (log/error "Job permanently failed"
               {:job-id   job-id
                :job-type job-type
                :attempts attempts
                :error    (ex-message exception)})
    ;; Optionally update domain state to reflect the failure
    (case job-type
      ::process-payment
      (db/update-payment-status! (:payment-id payload) :failed)

      ::send-onboarding-email
      (db/mark-onboarding-email-failed! (:user-id payload))

      nil)))

(def my-worker
  (worker/create-queue-worker
    data-source
    handle-job!
    {:proletarian/retry-strategy-fn my-retry-strategy
     :proletarian/failed-job-fn     on-failed-job}))
Common uses for failed-job-fn:
  • Alerting — log at ERROR level or send a notification to Sentry, PagerDuty, etc.
  • Domain state updates — mark an order, payment, or user record as failed so the application can surface the error to the end user.
  • Dead-letter queue — write the job details to a separate table for manual inspection and replay.

Archived Job Status

Both successfully completed jobs and permanently failed jobs end up in the archived_job table (default: proletarian.archived_job). The status column distinguishes them:
StatusMeaning
:successHandler completed without throwing
:failureHandler exhausted all retries
You can query this table directly for auditing, dashboards, or replaying failed jobs.

Build docs developers (and LLMs) love