# Scheduled Tasks

> ScheduledTask is used to run code later or repeatedly with an explicit identifier, execution pool, and cancellation path.

Each task lives inside a `ScheduledTaskRegistry`. When you call `ScheduledTaskRegistry.start`, the registry reads `ScheduledTask.options()`, checks the identifier, schedules the work, and stores the resulting `ScheduledFuture`.

If `repeat` is set, the task runs at a fixed rate after `delay`. If `repeat` is `null`, the task runs once after `delay`.

This removes the need to wire `ScheduledExecutorService`, keep `ScheduledFuture` references, or build your own task lookup layer.

{% hint style="info" %}
Scheduled tasks are mainly used for background work that should run later or repeat during application runtime, such as cleanup jobs, maintenance operations, or periodic updates.
{% endhint %}

***

### Two ways to define a task

The library supports two task styles:

* Build a task inline with `ScheduledTask.builder()`.
* Implement `ScheduledTask` in a class when the task has its own state or dependencies.

Use the builder for small jobs. Use a class when the task deserves a name, constructor arguments, or test coverage.

#### Inline task with the builder

This is the shortest way to schedule work:

```java
ScheduledTask cleanupTask = ScheduledTask.builder()
    .identifier("cleanup")
    .delay(Duration.ofSeconds(5))
    .repeat(Duration.ofMinutes(10))
    .schedulerPool(ScheduledTaskPoolType.NEW_PLATFORM_THREAD)
    .run(task -> performCleanup())
    .build();
```

The builder creates a `ScheduledTask` instance backed by `ScheduledTaskImpl`.

#### Task as a class

Use a class when the task should carry dependencies or internal state:

```java
public final class CleanupTask implements ScheduledTask {

    private final CacheService cacheService;

    public CleanupTask(final CacheService cacheService) {
        this.cacheService = cacheService;
    }

    @Override
    public ScheduledTaskOptions options() {
        return ScheduledTaskOptions.builder()
            .identifier("cleanup")
            .delay(Duration.ofSeconds(5))
            .repeat(Duration.ofMinutes(10))
            .schedulerPool(ScheduledTaskPoolType.NEW_PLATFORM_THREAD)
            .build();
    }

    @Override
    public void run(final RunningTask runningTask) {
        cacheService.cleanupExpiredEntries();
    }
}
```

This approach keeps configuration and execution logic together without forcing everything into one builder chain.

***

### Start a task - `start`

`ScheduledTaskRegistry.start` schedules a task using the values returned by `ScheduledTask.options()`.

```java
ScheduledTaskRegistry.common().start(
    ScheduledTask.builder()
        .identifier("cleanup")
        .delay(Duration.ofSeconds(5))
        .repeat(Duration.ofSeconds(10))
        .schedulerPool(pool)
        .run(task -> {
            IO.println("cleanup running");
        })
        .build()
);
```

When you start this task, the registry stores it under `"cleanup"`, waits 5 seconds, and then runs it every 10 seconds. If another active task already uses `"cleanup"`, the second start fails with `SchedulerDuplicateIdentifierException`.

Without `ScheduledTaskRegistry`, scheduling must be managed manually per executor instance:

```java
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
ScheduledFuture<?> future = executor.scheduleAtFixedRate(() -> {
    IO.println("cleanup running");
}, 5, 10, TimeUnit.SECONDS);
```

#### One-time task

If you do not set `repeat`, the task runs once:

```java
ScheduledTaskRegistry.common().start(
    ScheduledTask.builder()
        .identifier("warmup")
        .delay(Duration.ofSeconds(2))
        .schedulerPool(ScheduledTaskPoolType.SINGLE_THREAD_FOR_ALL_TASKS)
        .run(task -> preloadCache())
        .build()
);
```

This uses `schedule`, not `scheduleAtFixedRate`.

***

### Task configuration - `ScheduledTaskOptions`

`ScheduledTaskOptions` contains the values the registry uses when the task is started.

```java
ScheduledTaskOptions(String identifier, Duration delay, Duration repeat, ScheduledExecutorService scheduledExecutorService)
```

The `identifier` is used to store the task in the registry. When you call `start` again with the same identifier, the registry checks whether that identifier is already present and rejects the second task if it is.

The `delay` value is used before the first execution. The `repeat` value is used between executions. If `repeat` is `null`, the registry schedules the task once. If `repeat` is present, the registry schedules it at a fixed rate.

The `scheduledExecutorService` is the executor used to run the task.

#### Build options explicitly

This is useful when the task is implemented as a class:

```java
ScheduledTaskOptions options = ScheduledTaskOptions.builder()
    .identifier("cleanup")
    .delay(Duration.ofSeconds(5))
    .repeat(Duration.ofMinutes(10))
    .schedulerPool(ScheduledTaskPoolType.NEW_PLATFORM_THREAD)
    .build();
```

`ScheduledTaskOptions.Builder` also supports `randomIdentifier()` when the task should not use a fixed id:

```java
ScheduledTaskOptions options = ScheduledTaskOptions.builder()
    .randomIdentifier()
    .schedulerPool(ScheduledTaskPoolType.NEW_PLATFORM_THREAD)
    .build();
```

Use a stable identifier when you plan to cancel or inspect the task later by name. Use a random identifier when uniqueness matters more than later lookup.

#### Builder defaults

The two builders behave differently:

* `ScheduledTask.builder()` generates a random identifier by default.
* `ScheduledTaskOptions.builder()` requires you to set an identifier or call `randomIdentifier()`.
* Both builders default `delay` to `Duration.ofMillis(0)`.
* A missing `repeat` means the task runs once.
* Both builders require an executor source through `schedulerPool(...)`.

***

### Choose an execution pool

Every task must provide a scheduler pool. You can pass either a `ScheduledTaskPool` or a raw `ScheduledExecutorService`.

#### Built-in pool types - `ScheduledTaskPoolType`

The library ships with three common strategies:

* `SINGLE_THREAD_FOR_ALL_TASKS` — one shared scheduler thread for everything.
* `NEW_PLATFORM_THREAD` — a scheduled platform-thread pool.
* `NEW_VIRTUAL_THREAD` — timing on one scheduler, execution on a new virtual thread per run.

Use `SINGLE_THREAD_FOR_ALL_TASKS` for very small tasks. A slow task can delay the rest.

Use `NEW_PLATFORM_THREAD` for ordinary repeated work that may overlap or block briefly.

Use `NEW_VIRTUAL_THREAD` when task bodies may block and virtual threads are acceptable in your runtime.

#### Using a predefined pool

```java
ScheduledTask.builder()
    .identifier("cleanup")
    .repeat(Duration.ofMinutes(1))
    .schedulerPool(ScheduledTaskPoolType.NEW_VIRTUAL_THREAD)
    .run(task -> performCleanup())
    .build();
```

#### Using your own executor

If you already manage an executor elsewhere, pass it directly:

```java
ScheduledExecutorService executor = Executors.newScheduledThreadPool(4);

ScheduledTask task = ScheduledTask.builder()
    .identifier("cleanup")
    .repeat(Duration.ofMinutes(1))
    .schedulerPool(executor)
    .run(runningTask -> performCleanup())
    .build();
```

#### Custom pool implementation

You can also implement `ScheduledTaskPool`:

```java
public final class AppSchedulerPool implements ScheduledTaskPool {

    private final ScheduledExecutorService executor =
        Executors.newScheduledThreadPool(4);

    @Override
    public ScheduledExecutorService getScheduledExecutorService() {
        return executor;
    }
}
```

This is useful when scheduling should follow your own application wiring.

***

### Running task handle - `RunningTask`

`RunningTask` is passed to `run` on each execution. It lets the task cancel itself through the owning registry.

#### Cancelling from inside the task - Example

```java
ScheduledTaskRegistry.common().start(
    ScheduledTask.builder()
        .identifier("cleanup")
        .repeat(Duration.ofSeconds(10))
        .schedulerPool(pool)
        .run(task -> {
            if (shouldStopCleanup()) {
                task.cancel();
                return;
            }

            IO.println("cleanup running");
        })
        .build()
);
```

When `task.cancel()` is called, the registry removes the task entry and cancels the stored `ScheduledFuture`.

#### Stop after a condition is met

This is a common pattern for repeated tasks:

```java
AtomicInteger runs = new AtomicInteger();

ScheduledTaskRegistry.common().start(
    ScheduledTask.builder()
        .identifier("retry-sync")
        .repeat(Duration.ofSeconds(30))
        .schedulerPool(ScheduledTaskPoolType.NEW_PLATFORM_THREAD)
        .run(task -> {
            if (runs.incrementAndGet() >= 5) {
                task.cancel();
                return;
            }

            syncRemoteState();
        })
        .build()
);
```

This keeps stop logic inside the task body instead of outside scheduler code.

***

### Registry access - `common` and `create`

`ScheduledTaskRegistry.common` returns a shared registry instance. `ScheduledTaskRegistry.create` returns a new independent registry.

Tasks are tracked per registry instance. A task identifier only has to be unique inside the registry where the task is started.

#### Shared registry

Use `common()` when unrelated parts of the application should interact with the same task registry:

```java
ScheduledTaskRegistry registry = ScheduledTaskRegistry.common();
```

#### Independent registry

Use `create()` when a component should own its own scheduled tasks:

```java
ScheduledTaskRegistry registry = ScheduledTaskRegistry.create();
```

This is useful for modules, tests, or subsystems that should be started and stopped independently.

### Task state and cancellation

`ScheduledTaskRegistry.cancel` removes a task by identifier and cancels its stored `ScheduledFuture`. `ScheduledTaskRegistry.cancelAll` removes and cancels every task in that registry. `ScheduledTaskRegistry.isTaskRunning` checks whether an identifier is still stored in the registry.

```java
ScheduledTaskRegistry.common().cancel("cleanup");
ScheduledTaskRegistry.common().cancelAll();
ScheduledTaskRegistry.common().isTaskRunning("cleanup");
```

You can also cancel by passing the task instance:

```java
ScheduledTask cleanupTask = ScheduledTask.builder()
    .identifier("cleanup")
    .repeat(Duration.ofMinutes(1))
    .schedulerPool(ScheduledTaskPoolType.NEW_PLATFORM_THREAD)
    .run(task -> performCleanup())
    .build();

ScheduledTaskRegistry.common().start(cleanupTask);
ScheduledTaskRegistry.common().cancel(cleanupTask);
```

Use identifier-based cancellation when callers only know the task name. Use instance-based cancellation when the caller already holds the task object.

***

### Lifetime of tasks

Tasks in `ScheduledTaskRegistry` are created when `ScheduledTaskRegistry.start` is called.

The registry stores the resulting `ScheduledFuture` under the task identifier and keeps that entry until it is removed through `cancel`, `cancelAll`, or until the application stops.

This means cleanup must be done manually through the registry. One-time tasks are also not removed automatically after they finish running.

{% hint style="warning" %}
If you schedule a one-time task and never cancel it, `isTaskRunning` may still return `true` because the registry still holds its entry.
{% endhint %}

### What scheduled tasks give you

`ScheduledTask` is small, but it covers the core parts of scheduled work:

* Explicit task identity through `identifier`.
* Delayed or repeated execution through `delay` and `repeat`.
* Inline tasks or class-based tasks.
* Shared, custom, or direct executor selection.
* Registry-based cancellation from outside the task.
* Self-cancellation from inside the task.
* Registry isolation with `common()` or `create()`.

That keeps scheduled work visible in code instead of spreading it across raw executors, futures, and ad hoc tracking maps.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.ranked.wtf/core-concepts/scheduled-tasks.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
