flowable/flowable-engine · error · FlowableIllegalArgumentException

The number of job retries must be a non-negative Integer…

Error message

The number of job retries must be a non-negative Integer, but '${retries}' has been provided.

What it means

Flowable throws this FlowableIllegalArgumentException in the SetTimerJobRetriesCmd constructor when the retries argument is negative. Retry counts must be zero or positive because they set how many attempts remain for the timer job; a negative value has no valid meaning in the persistence model.

Solutions

  1. Clamp the computed value before calling: Math.max(0, retries)
  2. Validate the retries input at the API boundary before constructing the command
  3. Fix the decrement logic that can produce negative counts

Example fix

// before
int retries = maxRetries - failureCount;
managementService.setTimerJobRetries(jobId, retries);
// after
int retries = Math.max(0, maxRetries - failureCount);
managementService.setTimerJobRetries(jobId, retries);
Defensive patterns

Strategy: validation

Validate before calling

if (retries < 0) {
    throw new IllegalArgumentException("retries must be >= 0");
}
managementService.setTimerJobRetries(jobId, retries);

Prevention

When it happens

Trigger: Calling new SetTimerJobRetriesCmd(jobId, -1, config) or any negative retries value, typically when retries is computed (e.g. maxRetries - failureCount) and the computation underflows below zero.

Common situations: Computing remaining retries with an off-by-one decrement loop; passing an unvalidated user/config-provided retry count; arithmetic on int overflow producing a negative value.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11). Data as JSON: /api/errors/5cbe921b439257cb. Report an issue: GitHub.

Appendix: source

Thrown at modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/cmd/SetTimerJobRetriesCmd.java:45

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()) {
                eventDispatcher.dispatchEvent(FlowableJobEventBuilder.createEntityEvent(FlowableEngineEventType.ENTITY_UPDATED, job),
                        jobServiceConfiguration.getEngineName());
            }

View on GitHub (pinned to d6d39ce1c6)