flowable/flowable-engine · error · FlowableIllegalArgumentException
topic must not be empty
Error message
topic must not be empty
What it means
Thrown by AcquireExternalWorkerJobsCmd.execute() when the topic on the ExternalWorkerJobAcquireBuilder is null or empty. The topic is the category workers subscribe to, and the acquisition query filters jobs by it; without it Flowable cannot build a meaningful acquisition and fails fast.
Solutions
- Call topic("yourTopic") on the ExternalWorkerJobAcquireBuilder before acquiring
- Externalize the topic into worker configuration and validate it at startup (fail fast if blank)
- Match the topic exactly to the one set on the business job (jobServiceConfig setting externalWorkerJobTopic / setJobTopic)
Example fix
// before
ExternalWorkerJobAcquireBuilder builder = managementService.createExternalWorkerJobAcquireBuilder();
List<AcquiredExternalWorkerJob> jobs = builder.acquireAndLock(numberOfJobs, workerId, lockDuration);
// after
List<AcquiredExternalWorkerJob> jobs = managementService.createExternalWorkerJobAcquireBuilder()
.topic("myTopic")
.acquireAndLock(numberOfJobs, workerId, lockDuration); Defensive patterns
Strategy: validation
Validate before calling
if (topic == null || topic.isEmpty()) {
throw new IllegalArgumentException("topic must be configured before acquiring jobs");
} Type guard
boolean hasTopic(String t) { return t != null && !t.trim().isEmpty(); } Try / catch
try {
acquireBuilder.topic(topic).acquireAndLock(n, workerId, lockDuration);
} catch (FlowableIllegalArgumentException e) {
LOGGER.error("Acquire rejected: {}", e.getMessage());
} Prevention
- Validate topic configuration at worker startup
- Keep topic values in a shared constant used by both job creation and acquisition
- Reject blank topics at the config-loading layer
When it happens
Trigger: managementService.createExternalWorkerJobAcquireBuilder().acquireAndLock(...) without topic(...); passing a null/empty topic from configuration; building the acquire request programmatically from user input where topic was optional.
Common situations: Misconfigured worker deployment where the topic property is missing; a generic dispatcher that forgot to set the topic; renaming topics across versions and leaving the old key unset.
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
- requested number of jobs must not be smaller than 1
- workerId must not be empty
- A channel key detection value is required for the channel…
- A group or a user is required to create an identity link.
- A group or a user is required to create an identity link.
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/cf4e34dc72ec9a04.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/cmd/AcquireExternalWorkerJobsCmd.java:57
protected final String workerId;
protected final int numberOfJobs;
protected final ExternalWorkerJobAcquireBuilderImpl builder;
protected final JobServiceConfiguration jobServiceConfiguration;
public AcquireExternalWorkerJobsCmd(String workerId, int numberOfJobs, ExternalWorkerJobAcquireBuilderImpl builder,
JobServiceConfiguration jobServiceConfiguration) {
this.workerId = workerId;
this.numberOfJobs = numberOfJobs;
this.builder = builder;
this.jobServiceConfiguration = jobServiceConfiguration;
}
@Override
public List<AcquiredExternalWorkerJob> execute(CommandContext commandContext) {
String topic = builder.getTopic();
if (StringUtils.isEmpty(topic)) {
throw new FlowableIllegalArgumentException("topic must not be empty");
}
if (numberOfJobs < 1) {
throw new FlowableIllegalArgumentException("requested number of jobs must not be smaller than 1");
}
if (StringUtils.isEmpty(workerId)) {
throw new FlowableIllegalArgumentException("workerId must not be empty");
}
ExternalWorkerJobEntityManager externalWorkerJobEntityManager = jobServiceConfiguration.getExternalWorkerJobEntityManager();
InternalJobManager internalJobManager = jobServiceConfiguration.getInternalJobManager();
List<ExternalWorkerJobEntity> jobs = externalWorkerJobEntityManager.findExternalJobsToExecute(builder, numberOfJobs);
int lockTimeInMillis = (int) builder.getLockDuration().abs().toMillis();
List<AcquiredExternalWorkerJob> acquiredJobs = new ArrayList<>(jobs.size());
View on GitHub (pinned to d6d39ce1c6)