flowable/flowable-engine · error · FlowableException

Unable to peek empty agenda.

Error message

Unable to peek empty agenda.

What it means

Thrown by AbstractAgenda.assertOperationsNotEmpty when getNextOperation is asked to peek at the next plan item while both the operations queue and futureOperations are empty. It is an internal engine invariant: callers should only take the next operation when work has actually been planned.

Solutions

  1. Check operations/futureOperations are non-empty (agenda.planOperation(...) was called) before calling getNextOperation.
  2. In loops over the agenda, break out when operations are exhausted instead of calling getNextOperation again.
  3. If hit during normal engine operation with no custom agenda code, report a bug with the process model and command stack.

Example fix

// before
while (true) { agenda.getNextOperation().run(); }
// after
while (!agenda.getOperations().isEmpty()) { agenda.getNextOperation().run(); }
Defensive patterns

Strategy: type-guard

Validate before calling

if (agenda.getOperations().isEmpty() && agenda.getFutureOperations().isEmpty()) return; // nothing to execute

Type guard

boolean hasPendingWork(Agenda agenda) { return !agenda.getOperations().isEmpty() || !agenda.getFutureOperations().isEmpty(); }

Try / catch

try {
  agenda.getNextOperation().run();
} catch (FlowableException e) {
  if ("Unable to peek empty agenda.".equals(e.getMessage())) return; // agenda drained
  throw e;
}

Prevention

When it happens

Trigger: Calling agenda.getNextOperation() after all planned operations have executed (e.g. test code or custom code driving the agenda manually and looping one iteration too many).

Common situations: Custom engine extensions or tests that drive command-context operations directly and mis-handle the end-of-agenda condition; usually indicates an engine-internal bug or incorrect low-level agenda usage rather than a user configuration problem.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11). Data as JSON: /api/errors/15f514ea6027ac80. Report an issue: GitHub.

Appendix: source

Thrown at modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/agenda/AbstractAgenda.java:64

    }

    @Override
    public Runnable getNextOperation() {
        assertOperationsNotEmpty();
        if (!operations.isEmpty()) {
            return operations.poll();
        } else {
            // If there are no more operations then we need to wait until any of the schedule future operations are done
            List<ExecuteFutureActionOperation<?>> copyOperations = new ArrayList<>(futureOperations);
            futureOperations.clear();
            Duration maxWaitTimeout = getFutureMaxWaitTimeout();
            return new WaitForAnyFutureToFinishOperation(this, copyOperations, maxWaitTimeout);
        }
    }

    protected void assertOperationsNotEmpty() {
        if (operations.isEmpty() && futureOperations.isEmpty()) {
            throw new FlowableException("Unable to peek empty agenda.");
        }
    }

    /**
     * Generic method to plan a {@link Runnable}.
     */
    @Override
    public void planOperation(Runnable operation) {
        operations.add(operation);
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Operation {} added to agenda", operation.getClass());
        }
    }

    @Override
    public <V> void planFutureOperation(CompletableFuture<V> future, BiConsumer<V, Throwable> completeAction) {
        ExecuteFutureActionOperation<V> operation = new ExecuteFutureActionOperation<>(future, completeAction);
        if (future.isDone()) {

View on GitHub (pinned to d6d39ce1c6)