laravel/framework · error · RuntimeException
Attempted to batch job [%s], but it does not use the Batchab
Error message
Attempted to batch job [%s], but it does not use the Batchable trait.
What it means
PendingBatch::__construct()/add() calls ensureJobIsBatchable(), which uses class_uses_recursive() to verify each job uses the Illuminate\Bus\Batchable trait. Jobs in a batch must be Batchable so the framework can attach the batch ID and report progress. A missing trait throws this RuntimeException immediately.
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 bd6b5437e6)
Solutions
- Add 'use \Illuminate\Bus\Batchable;' inside the job class.
- Inside the job's handle(), use $this->batch() to access the batch (only available with the trait).
- If batching closures or nested PendingBatch instances, note those are exempt; do not pass raw non-Batchable objects.
Example fix
// before
class ProcessPodcast implements ShouldQueue
{
public function handle() { ... }
}
Bus::batch([new ProcessPodcast()])->dispatch();
// after
use Illuminate\Bus\Batchable;
class ProcessPodcast implements ShouldQueue
{
use Batchable;
public function handle()
{
$batch = $this->batch();
}
}
Bus::batch([new ProcessPodcast()])->dispatch(); Defensive patterns
Strategy: type-guard
Validate before calling
foreach ($jobs as $job) {
if (! in_array(\Illuminate\Bus\Batchable::class, class_uses_recursive($job), true)) {
throw new \LogicException(get_class($job).' must use the Batchable trait to be batched.');
}
}
Bus::batch($jobs)->dispatch(); Type guard
function isBatchable(object $job): bool
{
return isset(class_uses_recursive($job)[\Illuminate\Bus\Batchable::class]);
}
$batchable = array_filter($jobs, 'isBatchable'); Try / catch
try {
Bus::batch($jobs)->dispatch();
} catch (\RuntimeException $e) {
if (str_contains($e->getMessage(), 'does not use the Batchable trait')) {
// add the trait to the offending class and retry
}
throw $e;
} Prevention
- Make your base job class use Batchable.
- Run a static analysis rule that all classes passed to Bus::batch use Batchable.
- Document batchable job requirements in the project's job conventions.
When it happens
Trigger: Calling Bus::batch([new MyJob()])->dispatch() where MyJob extends a queueable base but does not 'use Batchable;'. Passing a plain object job into Bus::batch() or PendingBatch::add().
Common situations: Adding an existing queued job to a new batch without retrofitting the trait. Copying a job class that predates job batching. Forgetting the trait after scaffolding with make:job.
Related errors
- Queue resolver did not return a Queue implementation.
- To enable support for closure jobs, please install the illum
- Ably error: %s
- To enable support for closure jobs, please install the illum
- Cache driver not available. Scheduling unique jobs not suppo
AI-assisted analysis of laravel/framework@bd6b5437e6 (2026-08-06).
Data as JSON: /data/errors/cbd7e35c776e267b.json.
Report an issue: GitHub.