How do you cancel or reschedule a running scheduled task at runtime using ScheduledFuture and TaskScheduler?
answer
- scheduler.schedule(job, trigger) -> ScheduledFuture handle
- store handle in ConcurrentHashMap by job id
- cancel(false) = no more runs, let current finish
- cancel(true) = interrupt, cooperative only
- reschedule = cancel + schedule again; schedule may return null
basics
~10 sSchedule the task via a TaskScheduler, which returns a ScheduledFuture. Keep that reference; call future.cancel(mayInterruptIfRunning) to stop future executions. To reschedule, cancel the old future and call scheduler.schedule(...) again to get a new one.
solid answer
~40 sRather than registering the task at startup via addTriggerTask, inject a TaskScheduler (or ThreadPoolTaskScheduler) and call scheduler.schedule(runnable, trigger) yourself. That returns a ScheduledFuture<?> which is your handle to the task. Store it (e.g. in a Map keyed by job id). To stop it, call future.cancel(false) to let a running execution finish but prevent future ones, or cancel(true) to attempt to interrupt the in-flight thread. Cancelling only affects scheduling — it doesn't kill a task that ignores interruption. To reschedule with a new interval, cancel the existing future and call scheduler.schedule(...) again, replacing the stored reference. This gives an admin endpoint full start/stop/reschedule control. Watch for null (schedule returns null if the trigger's first nextExecution returns null) and guard concurrent access to the future map.
code
java · 19 lines@Service
class JobManager {
private final TaskScheduler scheduler;
private final Map<String, ScheduledFuture<?>> handles = new ConcurrentHashMap<>();
JobManager(TaskScheduler scheduler) { this.scheduler = scheduler; }
void start(String id, Runnable job, Trigger trigger) {
handles.compute(id, (k, existing) -> {
if (existing != null) existing.cancel(false); // replace atomically
return scheduler.schedule(job, trigger); // may be null
});
}
void stop(String id) {
ScheduledFuture<?> f = handles.remove(id);
if (f != null) f.cancel(false); // finish current run, no more after
}
}go deeper
Know that scheduler.schedule returns a ScheduledFuture and cancel() stops it.
Explain cancel(false) vs cancel(true) and reschedule = cancel + schedule again.
Manage handles in a thread-safe map, handle null returns, and know ScheduledTask as the Spring-native alternative.
Design graceful shutdown (pool lifecycle, await termination), cooperative interruption contracts, and atomic reschedule under concurrent admin calls.
## Why ScheduledFuture `addTriggerTask` in `configureTasks` is fine for tasks fixed at startup, but it gives you no *handle* to stop or replace a task later. For runtime control (an admin "pause job" button), schedule imperatively through a `TaskScheduler` and keep the returned `ScheduledFuture`. ## Scheduling and getting the handle ```java ScheduledFuture<?> future = taskScheduler.schedule(job, trigger); // or scheduler.scheduleAtFixedRate(...), scheduleWithFixedDelay(...) ``` `TaskScheduler.schedule(Runnable, Trigger)` returns a `ScheduledFuture<?>`. Note: it can return **null** if the trigger's very first `nextExecution` returns null (nothing to schedule). Store handles in a concurrent map keyed by job id so multiple jobs can be managed independently: ```java Map<String, ScheduledFuture<?>> handles = new ConcurrentHashMap<>(); ``` ## Cancellation semantics `ScheduledFuture` extends `Future`, so cancellation is `future.cancel(boolean mayInterruptIfRunning)`: - `cancel(false)` — prevents **future** executions; a currently running execution is allowed to complete. This is the usual, safe choice for scheduled jobs. - `cancel(true)` — additionally attempts to `interrupt()` the thread running the current execution. It only helps if the task is actually responsive to interruption (checks `Thread.interrupted()`, or is blocked in an interruptible call). A CPU-bound loop that never checks the flag will keep running. Cancellation is **idempotent-ish**: once cancelled, `isCancelled()` returns true; calling cancel again returns false. ## Rescheduling There is no "change the interval" method on `ScheduledFuture`. To reschedule: 1. `future.cancel(false)` the old handle. 2. Call `taskScheduler.schedule(job, newTrigger)` to get a fresh handle. 3. Replace the stored reference. Do this atomically (e.g. inside a `compute`/`synchronized` block on the map) to avoid a race where two handles run concurrently. ## Interaction with the trigger approach Both coexist. A common hybrid: a self-adjusting `Trigger` handles gradual interval changes each cycle, while cancel/reschedule handles hard stop/start and immediate application of a new schedule (since a `Trigger` change only takes effect after the currently pending fire). ## Gotchas - **Losing the reference** — if you don't store the `ScheduledFuture`, you can never cancel it. `addTriggerTask` alone gives you no handle (though `ScheduledTaskRegistrar.scheduleTriggerTask` returns a `ScheduledTask` you can cancel). - **null return** — guard against a null future before storing/cancelling. - **cancel(true) is not a kill switch** — it's cooperative; design tasks to check interruption for long-running work. - **Thread pool shutdown** — cancelling tasks doesn't shut the pool; on app shutdown ensure the `TaskScheduler` is destroyed (`ThreadPoolTaskScheduler` handles this via its lifecycle; set `setWaitForTasksToCompleteOnShutdown` / `setAwaitTerminationSeconds` for graceful drain). - **Concurrency on the handle map** — admin endpoints may call start/stop concurrently; use a thread-safe map and atomic swap. - **ScheduledTask alternative** — `ScheduledTaskRegistrar.scheduleTriggerTask(...)` returns a Spring `ScheduledTask` whose `cancel()` is a cleaner abstraction than raw `ScheduledFuture` and integrates with Spring's task tracking/actuator.
- What is the difference between cancel(false) and cancel(true)?cancel(false) stops future executions but lets an in-flight run finish. cancel(true) also interrupts the running thread — but only cooperatively: the task must check Thread.interrupted() or be in an interruptible blocking call, otherwise it keeps running.
- You call addTriggerTask in configureTasks but later need to cancel that task. What's the problem and fix?addTriggerTask returns void, so you have no handle. Use ScheduledTaskRegistrar.scheduleTriggerTask (returns a Spring ScheduledTask with cancel()), or schedule imperatively via TaskScheduler.schedule and keep the ScheduledFuture.
- Why might TaskScheduler.schedule(job, trigger) return null?If the trigger's first nextExecution returns null there is nothing to schedule, so the returned ScheduledFuture is null. You must null-check before storing or cancelling it.
saying these in an interview costs you the question
- Believing cancel(true) forcibly kills any running task regardless of its code.
- Assuming addTriggerTask gives you a handle to cancel later (it returns void).
- Not storing the ScheduledFuture, making cancellation impossible.
- Ignoring the possible null return from schedule.