laravel/framework · error · RuntimeException

Attempted to batch job

Error message

Attempted to batch job [%s], but it does not use the Batchable trait.

What it means

PendingBatch::ensureJobIsBatchable guards Bus::batch() against jobs that cannot participate in a batch. A batched job must use the Illuminate\Bus\Batchable trait so the framework can inject batch metadata (batchId) and the job can query/cancel its own batch. The check is cached per class (static $batchableClasses) for performance.

Solutions

  1. Add 'use Illuminate\Bus\Batchable;' at the top of the job class body and import the trait.
  2. If the job is intentionally non-batchable, remove it from the Bus::batch([...]) array and dispatch it separately.
  3. For closure/invokable batch entries wrapped via dispatch() helpers, ensure the underlying job class uses Batchable.

Example fix

// before
class ProcessPayment implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function handle(): void { /* ... */ }
}

Bus::batch([new ProcessPayment($invoice)]])->dispatch();

// after
class ProcessPayment implements ShouldQueue
{
    use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function handle(): void { /* ... */ }
}
Defensive patterns

Strategy: validation

Validate before calling

// Validate all jobs in a batch use Batchable before dispatching
$jobs = [new ProcessPayment($invoice), new SendReceipt($invoice)];
foreach ($jobs as $job) {
    if (! isset(class_uses_recursive($job)[\Illuminate\Bus\Batchable::class])) {
        throw new \LogicException(get_class($job) . ' must use the Batchable trait to be batched.');
    }
}
Bus::batch($jobs)->dispatch();

Type guard

function isBatchableJob(object|string $job): bool
{
    return isset(class_uses_recursive(is_object($job) ? $job::class : $job)[\Illuminate\Bus\Batchable::class]);
}

Try / catch

try {
    Bus::batch($jobs)->dispatch();
} catch (\RuntimeException $e) {
    if (str_starts_with($e->getMessage(), 'Attempted to batch job')) {
        // log which job class failed the check
        logger()->error('Non-batchable job submitted to batch', ['message' => $e->getMessage()]);
    }
    throw $e;
}

Prevention

When it happens

Trigger: Calling Bus::batch([new ProcessPayment($invoice)]) or ->add([...]) on a PendingBatch where ProcessPayment does not 'use Batchable'. Also triggered when chaining ->dispatch() on a PendingBatch that includes a job whose class was stripped of the trait in a refactor.

Common situations: Job was authored before batches existed and never updated. Developer copied a ShouldQueue job template that omits Batchable. Trait removed during cleanup because IDE flagged it as 'unused' (it's used via reflection).

Related errors


AI-assisted analysis of laravel/framework@e0f6eb3518 (2026-08-11). Data as JSON: /api/errors/cbd7e35c776e267b. Report an issue: GitHub.

Appendix: source

Thrown at src/Illuminate/Bus/PendingBatch.php:110

    /**
     * Ensure the given job is batchable.
     *
     * @param  object|array  $job
     * @return void
     *
     * @throws \RuntimeException
     */
    protected function ensureJobIsBatchable(object|array $job): void
    {
        foreach (Arr::wrap($job) as $job) {
            if ($job instanceof PendingBatch || $job instanceof Closure) {
                return;
            }

            if (! (static::$batchableClasses[$job::class] ?? false) && ! isset(class_uses_recursive($job)[Batchable::class])) {
                static::$batchableClasses[$job::class] = false;

                throw new RuntimeException(sprintf('Attempted to batch job [%s], but it does not use the Batchable trait.', $job::class));
            }

            static::$batchableClasses[$job::class] = true;
        }
    }

    /**
     * Add a callback to be executed when the batch is stored.
     *
     * @param  callable  $callback
     * @return $this
     */
    public function before($callback)
    {
        $this->registerCallback('before', $callback);

        return $this;
    }

View on GitHub (pinned to e0f6eb3518)