Quarkus - 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, clustering and programmatic scheduling of jobs.

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

If you annotate a method with @io.quarkus.scheduler.Scheduled it is automatically scheduled for invocation. In fact, such a method must be a non-private non-static method of a CDI bean. As a consequence of being a method of a CDI bean a scheduled method can be annotated with interceptor bindings, such as @javax.transaction.Transactional and @org.eclipse.microprofile.metrics.annotation.Counted.

If there is no CDI scope defined on the declaring class 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.

1.1. Triggers

A trigger is defined either by the @Scheduled#cron() or by the @Scheduled#every() attributes. 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

  1. @Scheduled(cron = "0 15 10 * * ?")
  2. 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.

If a CRON expression starts with { and ends with } then the scheduler attempts to find a corresponding config property and use the configured value instead.

CRON Config Property Example

  1. @Scheduled(cron = "{myMethod.cron.expr}")
  2. 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

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

If a value starts with { and ends with } then the scheduler attempts to find a corresponding config property and use the configured value instead.

Interval Config Property Example

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

1.2. Identity

By default, a unique id is generated for each scheduled method. This id is used in log messages and during debugging. Sometimes a possibility to specify an explicit id may come in handy.

Identity Example

  1. @Scheduled(identity = "myScheduledMethod")
  2. 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.

  1. @Scheduled(every = "2s", delay = 2, delayUnit = TimeUnit.HOUR) (1)
  2. void everyTwoSeconds() { }
1The 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”.

  1. @Scheduled(every = "2s", delayed = "2h")
  2. void everyTwoSeconds() { }
If @Scheduled#delay() is set to a value greater then zero the value of @Scheduled#delayed() is ignored.

The main advantage over @Scheduled#delay() is that the value is configurable. If the value starts with { and ends with } then the scheduler attempts to find a corresponding config property and use the configured value instead:

  1. @Scheduled(every = "2s", delayed = "{myMethod.delay.expr}") (1)
  2. void everyTwoSeconds() { }
1The 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().

  1. import static io.quarkus.scheduler.Scheduled.ConcurrentExecution.SKIP;
  2. @Scheduled(every = "1s", concurrentExecution = SKIP) (1)
  3. void nonConcurrent() {
  4. // we can be sure that this method is never executed concurrently
  5. }
1Concurrent executions are skipped.

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.

Scheduler Injection Example

  1. import io.quarkus.scheduler.Scheduler;
  2. class MyService {
  3. @Inject
  4. Scheduler scheduler;
  5. void ping() {
  6. scheduler.pause(); (1)
  7. if (scheduler.isRunning()) {
  8. throw new IllegalStateException("This should never happen!");
  9. }
  10. scheduler.resume(); (2)
  11. }
  12. }
1Pause all triggers.
2Resume the scheduler.

3. Programmatic Scheduling

If you need to schedule a job programmatically you’ll need to add the Quartz extension and use the Quartz API direcly.

Programmatic Scheduling with Quartz API

  1. import org.quartz.Scheduler;
  2. class MyJobs {
  3. void onStart(@Observes StartupEvent event, Scheduler quartz) throws SchedulerException {
  4. JobDetail job = JobBuilder.newJob(SomeJob.class)
  5. .withIdentity("myJob", "myGroup")
  6. .build();
  7. Trigger trigger = TriggerBuilder.newTrigger()
  8. .withIdentity("myTrigger", "myGroup")
  9. .startNow()
  10. .withSchedule(SimpleScheduleBuilder.simpleSchedule()
  11. .withIntervalInSeconds(1)
  12. .repeatForever())
  13. .build();
  14. quartz.scheduleJob(job, trigger);
  15. }
  16. }
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. See also Quartz Configuration Reference.

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. Configuration Reference