# Scheduler Reference Guide

Modern applications often need to run specific tasks periodically. There are two scheduler extensions in Quarkus. The `quarkus-scheduler` extension brings the API and a lightweight in-memory scheduler implementation. The `quarkus-quartz` extension implements the API from the `quarkus-scheduler` extension and contains a scheduler implementation based on the Quartz library. You will only need `quarkus-quartz` for more advanced scheduling use cases, such as persistent tasks and clustering.

| If you add the `quarkus-quartz` dependency to your project the lightweight scheduler implementation from the `quarkus-scheduler` extension is automatically disabled.

## 1. Scheduled Methods

A method annotated with `@io.quarkus.scheduler.Scheduled` is automatically scheduled for invocation. A scheduled method must not be abstract or private. It may be either static or non-static. A scheduled method can be annotated with interceptor bindings, such as `@jakarta.transaction.Transactional` and `@org.eclipse.microprofile.metrics.annotation.Counted`.

| If there is a bean class that has no scope and declares at least one non-static method annotated with `@Scheduled` then `@Singleton` is used.

Furthermore, the annotated method must return `void` and either declare no parameters or one parameter of type `io.quarkus.scheduler.ScheduledExecution`.

| The annotation is repeatable so a single method could be scheduled multiple times.

| Subclasses never inherit the metadata of a `@Scheduled` method declared on a superclass. In the following example, the `everySecond()` method is only invoked upon the instance of `Jobs`.
    class Jobs {

       @Scheduled(every = "1s")
       void everySecond() {
         // ..do something
       }
    }

    @Singleton
    class MyJobs extends Jobs {
    }

A CDI event of type `io.quarkus.scheduler.SuccessfulExecution` is fired synchronously and asynchronously when an execution of a scheduled method is successful. A CDI event of type `io.quarkus.scheduler.FailedExecution` is fired synchronously and asynchronously when an execution of a scheduled method throws an exception.

### 1.1. Triggers

A trigger is defined either by the `@Scheduled#cron()` or by the `@Scheduled#every()` attribute. If both are specified, the cron expression takes precedence. If none is specified, the build fails with an `IllegalStateException`.

#### 1.1.1. CRON

A CRON trigger is defined by a cron-like expression. For example `"0 15 10 * * ?"` fires at 10:15am every day.

CRON Trigger Example

    @Scheduled(cron = "0 15 10 * * ?")
    void fireAt10AmEveryDay() { }

The syntax used in CRON expressions is controlled by the `quarkus.scheduler.cron-type` property. The values can be `cron4j`, `quartz`, `unix` and `spring`. `quartz` is used by default.

The `cron` attribute supports Property Expressions including default values and nested Property Expressions. (Note that "{property.path}" style expressions are still supported but don’t offer the full functionality of Property Expressions.)

CRON Config Property Example

    @Scheduled(cron = "${myMethod.cron.expr}")
    void myMethod() { }

If you wish to disable a specific scheduled method, you can set its cron expression to `"off"` or `"disabled"`.

application.properties

    myMethod.cron.expr=disabled

Property Expressions allow you to define a default value that is used, if the property is not configured.

CRON Config Property Example with default `0 0 15 ? * MON *`

    @Scheduled(cron = "${myMethod.cron.expr:0 0 15 ? * MON *}")
    void myMethod() { }

If the property `myMethod.cron.expr` is undefined or `null`, the default value (`0 0 15 ? * MON *`) will be used.

##### 1.1.1.1. Time Zones

The cron expression is evaluated in the context of the default time zone. However, it is also possible to associate the cron expression with a specific time zone.

Time Zone Example

    @Scheduled(cron = "0 15 10 * * ?", timeZone = "Europe/Prague") (1)
    void myMethod() { }

1 | The time zone ID is parsed using `java.time.ZoneId#of(String)`.

The `timeZone` attribute supports Property Expressions including default values and nested Property Expressions.

Time Zone Configuration Property Example

    @Scheduled(cron = "0 15 10 * * ?", timeZone = "{myMethod.timeZone}")
    void myMethod() { }

#### 1.1.2. Intervals

An interval trigger defines a period between invocations. The period expression is based on the ISO-8601 duration format `PnDTnHnMn.nS` and the value of `@Scheduled#every()` is parsed with `java.time.Duration#parse(CharSequence)`. However, if an expression starts with a digit then the `PT` prefix is added automatically. So for example, `15m` can be used instead of `PT15M` and is parsed as "15 minutes".

Interval Trigger Example

    @Scheduled(every = "15m")
    void every15Mins() { }

The `every` attribute supports Property Expressions including default values and nested Property Expressions. (Note that `"{property.path}"` style expressions are still supported but don’t offer the full functionality of Property Expressions.)

Interval Config Property Example

    @Scheduled(every = "${myMethod.every.expr}")
    void myMethod() { }

Intervals can be disabled by setting their value to `"off"` or `"disabled"`. So for example a Property Expression with the default value `"off"` can be used to disable the trigger if its Config Property has not been set.

Interval Config Property Example with a Default Value

    @Scheduled(every = "${myMethod.every.expr:off}")
    void myMethod() { }

### 1.2. Identity

By default, a unique identifier is generated for each scheduled method. This identifier is used in log messages, during debugging and as a parameter of some `io.quarkus.scheduler.Scheduler` methods. Therefore, a possibility to specify an explicit identifier may come in handy.

Identity Example

    @Scheduled(identity = "myScheduledMethod")
    void myMethod() { }

The `identity` attribute supports Property Expressions including default values and nested Property Expressions. (Note that `"{property.path}"` style expressions are still supported but don’t offer the full functionality of Property Expressions.)

Interval Config Property Example

    @Scheduled(identity = "${myMethod.identity.expr}")
    void myMethod() { }

### 1.3. Delayed Execution

`@Scheduled` provides two ways to delay the time a trigger should start firing at.

`@Scheduled#delay()` and `@Scheduled#delayUnit()` form the initial delay together.

    @Scheduled(every = "2s", delay = 2, delayUnit = TimeUnit.HOUR) (1)
    void everyTwoSeconds() { }

1 | The trigger fires for the first time two hours after the application start.

| The final value is always rounded to full second.

`@Scheduled#delayed()` is a text alternative to the properties above. The period expression is based on the ISO-8601 duration format `PnDTnHnMn.nS` and the value is parsed with `java.time.Duration#parse(CharSequence)`. However, if an expression starts with a digit, the `PT` prefix is added automatically. So for example, `15s` can be used instead of `PT15S` and is parsed as "15 seconds".

    @Scheduled(every = "2s", delayed = "2h")
    void everyTwoSeconds() { }

| If `@Scheduled#delay()` is set to a value greater than zero the value of `@Scheduled#delayed()` is ignored.

The main advantage over `@Scheduled#delay()` is that the value is configurable. The `delay` attribute supports Property Expressions including default values and nested Property Expressions. (Note that `"{property.path}"` style expressions are still supported but don’t offer the full functionality of Property Expressions.)

    @Scheduled(every = "2s", delayed = "${myMethod.delay.expr}") (1)
    void everyTwoSeconds() { }

1 | The config property `myMethod.delay.expr` is used to set the delay.

### 1.4. Concurrent Execution

By default, a scheduled method can be executed concurrently. Nevertheless, it is possible to specify the strategy to handle concurrent executions via `@Scheduled#concurrentExecution()`.

    import static io.quarkus.scheduler.Scheduled.ConcurrentExecution.SKIP;

    @Scheduled(every = "1s", concurrentExecution = SKIP) (1)
    void nonConcurrent() {
      // we can be sure that this method is never executed concurrently
    }

1 | Concurrent executions are skipped.

| A CDI event of type `io.quarkus.scheduler.SkippedExecution` is fired when an execution of a scheduled method is skipped.

| Note that only executions within the same application instance are considered. This feature is not intended to work across the cluster.

### 1.5. Conditional Execution

You can define the logic to skip any execution of a scheduled method via `@Scheduled#skipExecutionIf()`. The specified bean class must implement `io.quarkus.scheduler.Scheduled.SkipPredicate` and the execution is skipped if the result of the `test()` method is `true`.

    class Jobs {

       @Scheduled(every = "1s", skipExecutionIf = MyPredicate.class) (1)
       void everySecond() {
         // do something every second...
       }
    }

    @Singleton (2)
    class MyPredicate implements SkipPredicate {

       @Inject
       MyService service;

       boolean test(ScheduledExecution execution) {
           return !service.isStarted(); (3)
       }
    }

1 | A bean instance of `MyPredicate.class` is used to evaluate whether an execution should be skipped. There must be exactly one bean that has the specified class in its set of bean types, otherwise the build fails.
2 | The scope of the bean must be active during execution.
3 | `Jobs.everySecond()` is skipped until `MyService.isStarted()` returns `true`.

Note that this is an equivalent of the following code:

    class Jobs {

       @Inject
       MyService service;

       @Scheduled(every = "1s")
       void everySecond() {
         if (service.isStarted()) {
            // do something every second...
         }
       }
    }

The main idea is to keep the logic to skip the execution outside the scheduled business methods so that it can be reused and refactored easily.

| A CDI event of type `io.quarkus.scheduler.SkippedExecution` is fired when an execution of a scheduled method is skipped.

### 1.6. Non-blocking Methods

By default, a scheduled method is executed on the main executor for blocking tasks. As a result, a technology that is designed to run on a Vert.x event loop (such as Hibernate Reactive) cannot be used inside the method body. For this reason, a scheduled method that returns `java.util.concurrent.CompletionStage<Void>` or `io.smallrye.mutiny.Uni<Void>`, or is annotated with `@io.smallrye.common.annotation.NonBlocking` is executed on the Vert.x event loop instead.

    class Jobs {

       @Scheduled(every = "1s")
       Uni<Void> everySecond() { (1)
         // ...do something async
       }
    }

1 | The return type `Uni<Void>` instructs the scheduler to execute the method on the Vert.x event loop.

## 2. Scheduler

Quarkus provides a built-in bean of type `io.quarkus.scheduler.Scheduler` that can be injected and used to pause/resume the scheduler and individual scheduled methods identified by a specific `Scheduled#identity()`.

Scheduler Injection Example

    import io.quarkus.scheduler.Scheduler;

    class MyService {

       @Inject
       Scheduler scheduler;

       void ping() {
          scheduler.pause(); (1)
          scheduler.pause("myIdentity"); (2)
          if (scheduler.isRunning()) {
             throw new IllegalStateException("This should never happen!");
          }
          scheduler.resume("myIdentity"); (3)
          scheduler.resume(); (4)
          scheduler.getScheduledJobs(); (5)
          Trigger jobTrigger = scheduler.getScheduledJob("myIdentity"); (6)
          if (jobTrigger != null && jobTrigger.isOverdue()){ (7)
            // the job is late to the party.
          }
       }
    }

1 | Pause all triggers.
2 | Pause a specific scheduled method by its identity
3 | Resume a specific scheduled method by its identity
4 | Resume the scheduler.
5 | List all jobs in the scheduler.
6 | Get Trigger metadata for a specific scheduled job by its identity.
7 | You can configure the grace period for isOverdue() with quarkus.scheduler.overdue-grace-period

| A CDI event is fired synchronously and asynchronously when the scheduler or a scheduled job is paused/resumed. The payload is `io.quarkus.scheduler.SchedulerPaused`, `io.quarkus.scheduler.SchedulerResumed`, `io.quarkus.scheduler.ScheduledJobPaused` and `io.quarkus.scheduler.ScheduledJobResumed` respectively.

## 3. Programmatic Scheduling

An injected `io.quarkus.scheduler.Scheduler` can be also used to schedule a job programmatically.

Programmatic Scheduling

    import io.quarkus.scheduler.Scheduler;

    @ApplicationScoped
    class MyJobs {

        @Inject
        Scheduler scheduler;

        void addMyJob() { (1)
            scheduler.newJob("myJob")
                .setCron("0/5 * * * * ?")
                .setTask(executionContext -> { (2)
                    // do something important every 5 seconds
                })
                .schedule(); (3)
        }

        void removeMyJob() {
            scheduler.unscheduleJob("myJob"); (4)
        }
    }

1 | This is a programmatic alternative to a method annotated with `@Scheduled(identity = "myJob", cron = "0/5 * * * * ?")`.
2 | The business logic is defined in a callback.
3 | The job is scheduled once the `JobDefinition#schedule()` method is called.
4 | A job that was added programmatically can be also removed.

| By default, the scheduler is not started unless a `@Scheduled` business method is found. You may need to force the start of the scheduler for "pure" programmatic scheduling via `quarkus.scheduler.start-mode=forced`.

| If the Quartz extension is present then the Quartz API can be also used to schedule a job programmatically.

## 4. Scheduled Methods and Testing

It is often desirable to disable the scheduler when running the tests. The scheduler can be disabled through the runtime config property `quarkus.scheduler.enabled`. If set to `false` the scheduler is not started even though the application contains scheduled methods. You can even disable the scheduler for particular Test Profiles.

## 5. Metrics

Some basic metrics are published out of the box if `quarkus.scheduler.metrics.enabled` is set to `true` and a metrics extension is present.

If the Micrometer extension is present, then a `@io.micrometer.core.annotation.Timed` interceptor binding is added to all `@Scheduled` methods automatically (unless it’s already present) and a `io.micrometer.core.instrument.Timer` with name `scheduled.methods` and a `io.micrometer.core.instrument.LongTaskTimer` with name `scheduled.methods.running` are registered. The fully qualified name of the declaring class and the name of a `@Scheduled` method are used as tags.

If the SmallRye Metrics extension is present, then a `@org.eclipse.microprofile.metrics.annotation.Timed` interceptor binding is added to all `@Scheduled` methods automatically (unless it’s already present) and a `org.eclipse.microprofile.metrics.Timer` is created for each `@Scheduled` method. The name consists of the fully qualified name of the declaring class and the name of a `@Scheduled` method. The timer has a tag `scheduled=true`.

## 6. OpenTelemetry Tracing

If `quarkus.scheduler.tracing.enabled` is set to `true` and the OpenTelemetry extension is present then the `@io.opentelemetry.instrumentation.annotations.WithSpan` annotation is added automatically to every `@Scheduled` method. As a result, each execution of this method has a new `io.opentelemetry.api.trace.Span` associated.

## 7. Configuration Reference

Configuration property fixed at build time - All other configuration properties are overridable at runtime

Configuration property | Type | Default
--- | --- | ---
`quarkus.scheduler.cron-type` The syntax used in CRON expressions. Environment variable: `QUARKUS_SCHEDULER_CRON_TYPE` | `cron4j`, `quartz`, `unix`, `spring`, `spring53` | `quartz`
`quarkus.scheduler.metrics.enabled` Scheduled task metrics will be enabled if a metrics extension is present and this value is true. Environment variable: `QUARKUS_SCHEDULER_METRICS_ENABLED` | boolean | `false`
`quarkus.scheduler.tracing.enabled` Tracing will be enabled if the OpenTelemetry extension is present and this value is true. Environment variable: `QUARKUS_SCHEDULER_TRACING_ENABLED` | boolean | `false`
`quarkus.scheduler.enabled` If schedulers are enabled. Environment variable: `QUARKUS_SCHEDULER_ENABLED` | boolean | `true`
`quarkus.scheduler.overdue-grace-period` Scheduled task will be flagged as overdue if next execution time is exceeded by this period. Environment variable: `QUARKUS_SCHEDULER_OVERDUE_GRACE_PERIOD` | Duration | `1S`
`quarkus.scheduler.start-mode` Scheduler can be started in different modes. By default, the scheduler is not started unless a `io.quarkus.scheduler.Scheduled` business method is found. Environment variable: `QUARKUS_SCHEDULER_START_MODE` | `normal`, tooltip:forced[The scheduler will be started even if no scheduled business methods are found. This is necessary for "pure" programmatic scheduling.], tooltip:halted[Just like the `forced` mode but the scheduler will not start triggering jobs until `Scheduler#resume()` is called. This can be useful to run some initialization logic that needs to be performed before the scheduler starts.] |

| About the Duration format The format for durations uses the standard `java.time.Duration` format. You can learn more about it in the Duration#parse() javadoc. You can also provide duration values starting with a number. In this case, if the value consists only of a number, the converter treats the value as seconds. Otherwise, `PT` is implicitly prepended to the value to obtain a standard `java.time.Duration` format.