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
- Check operations/futureOperations are non-empty (agenda.planOperation(...) was called) before calling getNextOperation.
- In loops over the agenda, break out when operations are exhausted instead of calling getNextOperation again.
- 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
- Only drive the agenda loop while operations are planned.
- Avoid custom code calling getNextOperation directly; use the engine's operation execution APIs.
- Treat this as an internal invariant bug if it appears without custom agenda usage.
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
- at least one of userId or groups must be provided
- callbackIds is null or empty
- callbackIds is null or empty
- Candidate group list is empty
- Candidate group list is empty
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)