Netflix/Hystrix · error · IllegalStateException

Hystrix does not support delayed scheduling

Error message

Hystrix does not support delayed scheduling

What it means

ThreadPoolScheduler - the RxJava Scheduler Hystrix uses for thread-isolated commands - only supports immediate scheduling onto the Hystrix thread pool. Its schedule(action, delayTime, unit) overload deliberately throws IllegalStateException because delayed tasks have no defined thread-pool semantics in Hystrix.

Source

Thrown at hystrix-core/src/main/java/com/netflix/hystrix/strategy/concurrency/HystrixContextScheduler.java:180

                return Subscriptions.unsubscribed();
            }

            // This is internal RxJava API but it is too useful.
            ScheduledAction sa = new ScheduledAction(action);

            subscription.add(sa);
            sa.addParent(subscription);

            ThreadPoolExecutor executor = (ThreadPoolExecutor) threadPool.getExecutor();
            FutureTask<?> f = (FutureTask<?>) executor.submit(sa);
            sa.add(new FutureCompleterWithConfigurableInterrupt(f, shouldInterruptThread, executor));

            return sa;
        }

        @Override
        public Subscription schedule(Action0 action, long delayTime, TimeUnit unit) {
            throw new IllegalStateException("Hystrix does not support delayed scheduling");
        }
    }

    /**
     * Very similar to rx.internal.schedulers.ScheduledAction.FutureCompleter, but with configurable interrupt behavior
     */
    private static class FutureCompleterWithConfigurableInterrupt implements Subscription {
        private final FutureTask<?> f;
        private final Func0<Boolean> shouldInterruptThread;
        private final ThreadPoolExecutor executor;

        private FutureCompleterWithConfigurableInterrupt(FutureTask<?> f, Func0<Boolean> shouldInterruptThread, ThreadPoolExecutor executor) {
            this.f = f;
            this.shouldInterruptThread = shouldInterruptThread;
            this.executor = executor;
        }

        @Override

View on GitHub (pinned to 5ce3bc58c3)

Solutions

  1. Apply delayed operators on a standard Rx scheduler (Schedulers.computation()/io()) instead of the Hystrix thread-pool scheduler.
  2. Reorder the chain: schedule only the command execution itself on Hystrix; do retry/delay/backpressure orchestration downstream on another scheduler.
  3. If you need delayed retries, use Hystrix's own retry mechanisms or an outer observable with retryWhen on a default scheduler.

Example fix

// before
command.toObservable()
    .delay(1, TimeUnit.SECONDS)          // forces delayed schedule
    .subscribeOn(hystrixScheduler)       // -> IllegalStateException

// after
command.toObservable()
    .delay(1, TimeUnit.SECONDS)
    .subscribeOn(Schedulers.computation());
Defensive patterns

Strategy: validation

Try / catch

try {
    observable.delay(1, TimeUnit.SECONDS).subscribeOn(hystrixScheduler);
} catch (IllegalStateException e) {
    if ("Hystrix does not support delayed scheduling".equals(e.getMessage())) {
        // restructure: use Schedulers.computation() for delayed operators
    }
}

Prevention

When it happens

Trigger: Using RxJava operators that introduce delays on a Hystrix-scheduled observable, e.g. observable.delay(...), timer(...), or interval(...) while subscribed on a HystrixContextScheduler/ThreadPoolScheduler.

Common situations: Chaining .delay() or .timer() onto a HystrixCommand's toObservable() and then calling subscribeOn with the Hystrix scheduler; custom Rx pipelines built on HystrixPlugins concurrency strategy schedulers.

Related errors


AI-assisted analysis of Netflix/Hystrix@5ce3bc58c3 (2026-08-14). Data as JSON: /api/errors/b5d79b93b98a4696. Report an issue: GitHub.