flowable/flowable-engine · error · IllegalArgumentException
there must be at least one job entity manager
Error message
there must be at least one job entity manager
What it means
ResetExpiredJobsRunnable is constructed with a varargs array of job entity managers it must scan to reset expired (locked-but-stale) jobs. The constructor enforces that at least one manager is supplied, throwing IllegalArgumentException otherwise. Without a manager the runnable would have nothing to reset, which is a programming error, not a runtime condition.
Source
Thrown at modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/asyncexecutor/ResetExpiredJobsRunnable.java:56
*/
public class ResetExpiredJobsRunnable implements Runnable {
private static final Logger LOGGER = LoggerFactory.getLogger(ResetExpiredJobsRunnable.class);
protected final String name;
protected final AsyncExecutor asyncExecutor;
protected final Collection<JobInfoEntityManager<? extends JobInfoEntity>> jobInfoEntityManagers;
protected volatile boolean isInterrupted;
protected final Object MONITOR = new Object();
protected final AtomicBoolean isWaiting = new AtomicBoolean(false);
public ResetExpiredJobsRunnable(String name, AsyncExecutor asyncExecutor,
JobInfoEntityManager<? extends JobInfoEntity>... jobEntityManagers) {
this.name = name;
this.asyncExecutor = asyncExecutor;
if (jobEntityManagers.length < 1) {
throw new IllegalArgumentException("there must be at least one job entity manager");
}
this.jobInfoEntityManagers = Arrays.asList(jobEntityManagers);
}
@Override
public synchronized void run() {
LOGGER.info("starting to reset expired jobs for engine {}", getEngineName());
Thread.currentThread().setName(name);
while (!isInterrupted) {
resetJobs();
// Sleep
try {
synchronized (MONITOR) {
if (!isInterrupted) {View on GitHub (pinned to d6d39ce1c6)
Solutions
- Pass at least one JobInfoEntityManager (e.g. jobEntityManager, timerJobEntityManager) when constructing the runnable.
- If using the standard DefaultAsyncJobExecutor, let it wire the runnable instead of constructing it manually.
- Guard custom bootstrap code with a length check / config source so managers are always provided.
Example fix
// before
resetExpiredJobsRunnable = new ResetExpiredJobsRunnable("reset", asyncExecutor);
// after
resetExpiredJobsRunnable = new ResetExpiredJobsRunnable("reset", asyncExecutor,
jobServiceConfiguration.getJobEntityManager(),
jobServiceConfiguration.getTimerJobEntityManager()); Defensive patterns
Strategy: validation
Validate before calling
if (jobEntityManagers == null || jobEntityManagers.length == 0) {
throw new IllegalArgumentException("ResetExpiredJobsRunnable requires at least one job entity manager");
} Prevention
- Always pass job and timer job entity managers to the constructor.
- Prefer letting DefaultAsyncJobExecutor wire the runnable.
- Add a constructor-level assertion in custom executor bootstrap code.
When it happens
Trigger: new ResetExpiredJobsRunnable(name, asyncExecutor) with an empty varargs array — typically in custom async executor bootstrap code that builds the runnable manually.
Common situations: Custom AsyncExecutor implementations that construct ResetExpiredJobsRunnable directly without passing the timer/message job entity managers; test scaffolding forgetting the managers.
Related errors
- Builder ${clazz} is missing constructor (can't pass features
- The input should always be even since we expect a list of ke
- Cannot delete
- Optimistic locking exception (using global acquire lock) for
- exception for engine {} during async job acquisition: {}
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/f25de5357cbd0ccf.
Report an issue: GitHub.