laravel/framework · error · LogicException

A debounced job cannot also implement ShouldBeUnique.

Error message

A debounced job cannot also implement ShouldBeUnique.

What it means

Thrown by PendingDispatch::acquireDebounceLock() when a job both declares a debounce window (via the DebounceFor attribute or a debounceFor property) and implements ShouldBeUnique. Debounce coalesces repeated dispatches of the same job into one using a lock keyed by the job identity; ShouldBeUnique prevents duplicate dispatches entirely. The two mechanisms conflict and could double-protect or deadlock, so Laravel rejects the combination at dispatch time as a LogicException (a programmer error, not a runtime fault).

Source

Thrown at src/Illuminate/Foundation/Bus/PendingDispatch.php:252

    /**
     * Acquire a debounce lock for the job and set its delay.
     *
     * @return void
     *
     * @throws LogicException
     */
    protected function acquireDebounceLock()
    {
        $debounceFor = $this->getAttributeValue($this->job, DebounceFor::class, 'debounceFor');

        if ($debounceFor === null) {
            return;
        }

        $lock = new DebounceLock(Container::getInstance()->make(Cache::class));

        if ($this->job instanceof ShouldBeUnique) {
            throw new LogicException('A debounced job cannot also implement ShouldBeUnique.');
        }

        $result = $lock->acquire(
            $this->job, $debounceFor
        );

        $this->job->debounceOwner = $result['owner'];

        if (is_null($this->job->delay)) {
            $this->job->delay = $result['maxWaitExceeded'] ? 0 : $debounceFor;
        }
    }

    /**
     * Get the underlying job instance.
     *
     * @return mixed
     */

View on GitHub (pinned to bd6b5437e6)

Solutions

  1. Remove `implements ShouldBeUnique` (and ShouldBeUniqueUntilProcessing) from the debounced job class — debounce already prevents duplicates.
  2. If you truly need uniqueness semantics, remove the DebounceFor attribute/property and rely on ShouldBeUnique instead.
  3. Use ShouldBeUnique on a different job than the debounced one and split responsibilities.

Example fix

// before
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Queue\Attributes\DebounceFor;

class SendDigest implements ShouldBeUnique
{
    #[DebounceFor(60)]
    public int $debounceFor = 60;
}

// after — pick one mechanism
class SendDigest
{
    #[DebounceFor(60)]
    public int $debounceFor = 60;
}
Defensive patterns

Strategy: type-guard

Validate before calling

use Illuminate\Contracts\Queue\ShouldBeUnique;

$job = new SomeJob();
if ($job instanceof ShouldBeUnique && (new \ReflectionClass($job))->hasProperty('debounceFor')) {
    throw new \LogicException('Cannot dispatch: job is both ShouldBeUnique and debounced.');
}

Type guard

use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Queue\Attributes\DebounceFor;

function isDebounced(object|string $job): bool
{
    $r = new \ReflectionClass(is_object($job) ? $job : $job);
    if ($r->hasProperty('debounceFor')) {
        return true;
    }
    return ! empty($r->getAttributes(DebounceFor::class));
}

// Guard dispatch:
if (isDebounced(SomeJob::class) && is_subclass_of(SomeJob::class, ShouldBeUnique::class)) {
    throw new \LogicException('Pick one: debounce OR ShouldBeUnique, not both.');
}

Try / catch

// LogicException indicates a programmer error; fix the class definition, do not catch at dispatch.

Prevention

When it happens

Trigger: Defining a job class that uses #[DebounceFor(60)] (or public property $debounceFor) and also `implements ShouldBeUnique` (or ShouldBeUniqueUntilProcessing). Triggered at dispatch: SomeJob::dispatch($payload); — the exception fires before the job reaches the queue.

Common situations: Adding ShouldBeUnique to an existing debounced job for 'extra safety'. Copying a unique-job pattern onto a job that was later given a debounce attribute. Upgrading Laravel to a version that introduced DebounceFor and combining both features without reading the constraint.

Related errors


AI-assisted analysis of laravel/framework@bd6b5437e6 (2026-08-06). Data as JSON: /data/errors/7dbc2a23839e3d4a.json. Report an issue: GitHub.