alibaba/spring-ai-alibaba · error · IllegalStateException

Schedule already started

Error message

Schedule already started

What it means

ScheduledAgentTask.start() throws IllegalStateException if the task was already started (started flag true). Each ScheduledAgentTask instance guards against double-scheduling, which would otherwise register duplicate executions with the taskScheduler.

Source

Thrown at spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/scheduling/ScheduledAgentTask.java:72

	private final String taskId;

	public ScheduledAgentTask(CompiledGraph graph, ScheduleConfig config) {
		this.graph = graph;
		this.config = config;
		ScheduledAgentManager scheduledAgentManager = ScheduledAgentManagerFactory.getInstance().getManager();
		this.taskScheduler = scheduledAgentManager.getTaskScheduler();
		// Register with the active manager
		this.taskId = scheduledAgentManager.registerTask(this);
		log.debug("Created ScheduledAgentTask with ID: {}", taskId);
	}

	/**
	 * Start the scheduled execution
	 */
	public ScheduledAgentTask start() {
		if (started) {
			throw new IllegalStateException("Schedule already started");
		}

		switch (config.getMode()) {
			case CRON:
				scheduledFuture = taskScheduler.schedule(this::executeGraph,
						new CronTrigger(config.getCronExpression()));
				break;
			case FIXED_DELAY:
				scheduledFuture = taskScheduler.scheduleWithFixedDelay(this::executeGraph,
						Instant.now().plusMillis(config.getInitialDelay()), Duration.ofMillis(config.getFixedDelay()));
				break;
			case FIXED_RATE:
				scheduledFuture = taskScheduler.scheduleAtFixedRate(this::executeGraph,
						Instant.now().plusMillis(config.getInitialDelay()), Duration.ofMillis(config.getFixedRate()));
				break;
			case ONE_TIME:
				scheduledFuture = taskScheduler.schedule(this::executeGraph,
						Instant.now().plusMillis(config.getInitialDelay()));

View on GitHub (pinned to f82da0b50f)

Solutions

  1. Track started tasks and only call start() once per instance (check a started flag or idempotent wrapper)
  2. Create a new ScheduledAgentTask instance for restart instead of reusing the old one
  3. Call stop()/cancel on the existing task before starting a new instance
  4. Wrap start() in an idempotency guard: if (taskStartedIds.add(taskId)) task.start();

Example fix

// before
task.start();
task.start(); // throws
// after
if (!task.isStarted()) {
    task.start();
}
Defensive patterns

Strategy: validation

Validate before calling

if (!task.isStarted()) {
    task.start();
}

Type guard

boolean canStart(ScheduledAgentTask t) {
    return t != null && !t.isStarted();
}

Try / catch

try {
    task.start();
} catch (IllegalStateException e) {
    if ("Schedule already started".equals(e.getMessage())) {
        // idempotent: already running, safe to ignore
    } else throw e;
}

Prevention

When it happens

Trigger: Calling start() twice on the same ScheduledAgentTask instance — e.g. restart logic that re-invokes start on an existing task, or configuration code that starts tasks both in initialization and in a scheduler scan.

Common situations: Application hot-reload calling start again; idempotency wrappers missing around task bootstrap; tests running setup twice.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of alibaba/spring-ai-alibaba@f82da0b50f (2026-09-09). Data as JSON: /api/errors/c5a67e9f24ce029a. Report an issue: GitHub.