flowable/flowable-engine · error · FlowableIllegalArgumentException
The job id is mandatory, but
Error message
The job id is mandatory, but '${jobId}' has been provided. What it means
Flowable throws this FlowableIllegalArgumentException in the SetTimerJobRetriesCmd constructor when the timer job id argument is null or an empty/blank string. The job id is the primary key used to locate the timer job entity, so without it the command can never succeed. It is fail-fast validation in the command constructor before any database access.
Solutions
- Ensure a non-empty timer job id is resolved before constructing the command
- Null/blank-check the id at the API boundary and surface a clear validation message to the caller
- Verify the id comes from the correct entity (a timer job id, not a process/execution id)
- Log the value being passed to catch empty-string propagation
Example fix
// before
String jobId = variables.get("jobId");
managementService.setTimerJobRetries(jobId, 3);
// after
String jobId = variables.get("jobId");
if (jobId == null || jobId.trim().isEmpty()) {
throw new IllegalArgumentException("timerJobId must be provided");
}
managementService.setTimerJobRetries(jobId, 3); Defensive patterns
Strategy: validation
Validate before calling
if (jobId == null || jobId.trim().isEmpty()) {
throw new IllegalArgumentException("A non-empty timer job id is required");
}
managementService.setTimerJobRetries(jobId, retries); Type guard
boolean isValidJobId(String id) { return id != null && !id.trim().isEmpty(); } Prevention
- Resolve job ids from a TimerJobQuery, not free-form input
- Validate ids at API boundaries before constructing commands
- Log the id value when constructing retry commands to catch empty propagation
When it happens
Trigger: Calling new SetTimerJobRetriesCmd(null, retries, config) or SetTimerJobRetriesCmd("", retries, config), or indirectly invoking ManagementService/setTimerJobRetries-style APIs where the jobId variable passed in was never initialized or came back empty from an upstream lookup.
Common situations: Passing a variable that was assigned from a map/getter that returned null; copying code from SetJobRetriesCmd and forgetting to supply the timer job id; deserializing a job id from config or a request payload where the field is optional and empty.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- 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.
- A process instance id is required, but the provided id '" +…
- A process instance id is required, but the provided id
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/916f24cef35a46f4.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/cmd/SetTimerJobRetriesCmd.java:42
import org.flowable.job.api.Job;
import org.flowable.job.service.JobServiceConfiguration;
import org.flowable.job.service.event.impl.FlowableJobEventBuilder;
import org.flowable.job.service.impl.persistence.entity.TimerJobEntity;
/**
* @author Tijs Rademakers
*/
public class SetTimerJobRetriesCmd implements Command<Void>, Serializable {
private static final long serialVersionUID = 1L;
protected final String jobId;
protected final int retries;
protected JobServiceConfiguration jobServiceConfiguration;
public SetTimerJobRetriesCmd(String jobId, int retries, JobServiceConfiguration jobServiceConfiguration) {
if (jobId == null || jobId.length() < 1) {
throw new FlowableIllegalArgumentException("The job id is mandatory, but '" + jobId + "' has been provided.");
}
if (retries < 0) {
throw new FlowableIllegalArgumentException("The number of job retries must be a non-negative Integer, but '" + retries + "' has been provided.");
}
this.jobId = jobId;
this.retries = retries;
this.jobServiceConfiguration = jobServiceConfiguration;
}
@Override
public Void execute(CommandContext commandContext) {
TimerJobEntity job = jobServiceConfiguration.getTimerJobEntityManager().findById(jobId);
if (job != null) {
job.setRetries(retries);
FlowableEventDispatcher eventDispatcher = jobServiceConfiguration.getEventDispatcher();
if (eventDispatcher != null && eventDispatcher.isEnabled()) {View on GitHub (pinned to d6d39ce1c6)