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

  1. Call topic("yourTopic") on the ExternalWorkerJobAcquireBuilder before acquiring
  2. Externalize the topic into worker configuration and validate it at startup (fail fast if blank)
  3. 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

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


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)