Quarkus - Scheduling Periodic Tasks with Quartz

Modern applications often need to run specific tasks periodically. In this guide, you learn how to schedule periodic clustered tasks using the Quartz extension.

This technology is considered preview.

In preview, backward compatibility and presence in the ecosystem is not guaranteed. Specific improvements might require to change configuration or APIs and plans to become stable are under way. Feedback is welcome on our mailing list or as issues in our GitHub issue tracker.

For a full list of possible extension statuses, check our FAQ entry.

If you only need to run in-memory scheduler use the Scheduler extension.


To complete this guide, you need:

  • less than 10 minutes

  • an IDE

  • JDK 1.8+ installed with JAVA_HOME configured appropriately

  • Apache Maven 3.6.2+

  • Docker and Docker Compose installed on your machine


In this guide, we are going to expose one Rest API tasks to visualise the list of tasks created by a Quartz job running every 10 seconds.


We recommend that you follow the instructions in the next sections and create the application step by step. However, you can go right to the completed example.

Clone the Git repository: git clone https://github.com/quarkusio/quarkus-quickstarts.git, or download an archive.

The solution is located in the quartz-quickstart directory.

Creating the Maven project

First, we need a new project. Create a new project with the following command:

mvn io.quarkus:quarkus-maven-plugin:1.13.7.Final:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=quartz-quickstart \
    -DclassName="org.acme.quartz.TaskResource" \
    -Dpath="/tasks" \
cd quartz-quickstart

It generates:

  • the Maven structure

  • a landing page accessible on http://localhost:8080

  • example Dockerfile files for both native and jvm modes

  • the application configuration file

  • an org.acme.quartz.TaskResource resource

  • an associated test

The Maven project also imports the Quarkus Quartz extension.

If you already have your Quarkus project configured, you can add the quartz extension to your project by running the following command in your project base directory:

./mvnw quarkus:add-extension -Dextensions="quartz"

This will add the following to your pom.xml:


To use a JDBC store, the quarkus-agroal extension, which provides the datasource support, is also required.

Creating the Task Entity

In the org.acme.quartz package, create the Task class, with the following content:

package org.acme.quartz;

import javax.persistence.Entity;
import java.time.Instant;
import javax.persistence.Table;

import io.quarkus.hibernate.orm.panache.PanacheEntity;

public class Task extends PanacheEntity { (1)
    public Instant createdAt;

    public Task() {
        createdAt = Instant.now();

    public Task(Instant time) {
        this.createdAt = time;
  1. Declare the entity using Panache

Creating a scheduled job

In the org.acme.quartz package, create the TaskBean class, with the following content:

package org.acme.quartz;

import javax.enterprise.context.ApplicationScoped;

import javax.transaction.Transactional;

import io.quarkus.scheduler.Scheduled;

@ApplicationScoped (1)
public class TaskBean {

    @Scheduled(every = "10s", identity = "task-job") (2)
    void schedule() {
        Task task = new Task(); (3)
        task.persist(); (4)
  1. Declare the bean in the application scope

  2. Use the @Scheduled annotation to instruct Quarkus to run this method every 10 seconds and set the unique identifier for this job.

  3. Create a new Task with the current start time.

  4. Persist the task in database using Panache.

Scheduling Jobs Programmatically

It is also possible to leverage the Quartz API directly. You can inject the underlying org.quartz.Scheduler in any bean:

package org.acme.quartz;

public class TaskBean {

    org.quartz.Scheduler quartz; (1)

    void onStart(@Observes StartupEvent event) throws SchedulerException {
       JobDetail job = JobBuilder.newJob(MyJob.class)
                         .withIdentity("myJob", "myGroup")
       Trigger trigger = TriggerBuilder.newTrigger()
                            .withIdentity("myTrigger", "myGroup")
       quartz.scheduleJob(job, trigger); (2)

    void performTask() {
        Task task = new Task();

    // A new instance of MyJob is created by Quartz for every job execution
    public static class MyJob implements Job {

       TaskBean taskBean;

       public void execute(JobExecutionContext context) throws JobExecutionException {
          taskBean.performTask(); (3)

  1. Inject the underlying org.quartz.Scheduler instance.

  2. Schedule a new job using the Quartz API.

  3. Invoke the TaskBean#performTask() method from the job. Jobs are also container-managed beans if they belong to a bean archive.

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.

Updating the application configuration file

Edit the application.properties file and add the below configuration:

# Quartz configuration
quarkus.quartz.clustered=true (1)
quarkus.quartz.store-type=jdbc-cmt (2)

# Datasource configuration.

# Hibernate configuration

# flyway configuration
  1. Indicate that the scheduler will be run in clustered mode

  2. Use the database store to persist job related information so that they can be shared between nodes

Updating the resource and the test

Edit the TaskResource class, and update the content to:

package org.acme.quartz;

import java.util.List;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

public class TaskResource {

    public List<Task> listAll() {
        return Task.listAll(); (1)
  1. Retrieve the list of created tasks from the database

We also need to update the tests. Edit the TaskResourceTest class to match:

package org.acme.quartz;

import io.quarkus.test.junit.QuarkusTest;

import static org.hamcrest.Matchers.greaterThanOrEqualTo;

import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;

public class TaskResourceTest {

    public void tasks() throws InterruptedException {
        Thread.sleep(1000); // wait at least a second to have the first task created
                .body("size()", is(greaterThanOrEqualTo(1))); (1)
  1. Ensure that we have a 200 response and at least one task created

Creating Quartz Tables

Add a SQL migration file named src/main/resources/db/migration/V2.0.0__QuarkusQuartzTasks.sql with the content copied from file with the content from V2.0.0__QuarkusQuartzTasks.sql.

Configuring the load balancer

In the root directory, create a nginx.conf file with the following content:

user  nginx;

events {
    worker_connections   1000;

http {
        server {
              listen 8080;
              location / {
                proxy_pass http://tasks:8080; (1)
  1. Route all traffic to our tasks application

Setting Application Deployment

In the root directory, create a docker-compose.yml file with the following content:

version: '3'

  tasks: (1)
    image: quarkus-quickstarts/quartz:1.0
      context: ./
      dockerfile: src/main/docker/Dockerfile.${QUARKUS_MODE:-jvm}
      QUARKUS_DATASOURCE_URL: jdbc:postgresql://postgres/quarkus_test
      - tasks-network
      - postgres

  nginx: (2)
    image: nginx:1.17.6
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - tasks
      - 8080:8080
      - tasks-network

  postgres: (3)
    image: postgres:13.1
    container_name: quarkus_test
      - POSTGRES_USER=quarkus_test
      - POSTGRES_PASSWORD=quarkus_test
      - POSTGRES_DB=quarkus_test
      - 5432:5432
      - tasks-network

    driver: bridge
  1. Define the tasks service

  2. Define the nginx load balancer to route incoming traffic to an appropriate node

  3. Define the configuration to run the database

Running the database

In a separate terminal, run the below command:

docker-compose up postgres (1)
  1. Start the database instance using the configuration options supplied in the docker-compose.yml file

Run the application in Dev Mode

Run the application with: ./mvnw quarkus:dev. After a few seconds, open another terminal and run curl localhost:8080/tasks to verify that we have at least one task created.

As usual, the application can be packaged using ./mvnw clean package and executed using the target/quarkus-app/quarkus-run.jar file. You can also generate the native executable with ./mvnw clean package -Pnative.

Packaging the application and run several instances

The application can be packaged using ./mvnw clean package. Once the build is successful, run the below command:

docker-compose up --scale tasks=2 --scale nginx=1 (1)
  1. Start two instances of the application and a load balancer

After a few seconds, in another terminal, run curl localhost:8080/tasks to verify that tasks were only created at different instants and in an interval of 10 seconds.

You can also generate the native executable with ./mvnw clean package -Pnative.

It’s the reponsibility of the deployer to clear/remove the previous state, i.e. stale jobs and triggers. Moreover, the applications that form the "Quartz cluster" should be identical, otherwise an unpredictable result may occur.

Registering Plugin and Listeners

You can register plugins, job-listeners and trigger-listeners through Quarkus configuration.

The example below registers the plugin org.quartz.plugins.history.LoggingJobHistoryPlugin named as jobHistory with the property jobSuccessMessage defined as Job [{1}.{0}] execution complete and reports: {8}

quarkus.quartz.plugins.jobHistory.properties.jobSuccessMessage=Job [{1}.{0}] execution complete and reports: {8}

You can also register a listener programmatically with an injected org.quartz.Scheduler:

public class MyListenerManager {
    void onStart(@Observes StartupEvent event, org.quartz.Scheduler scheduler) throws SchedulerException {
        scheduler.getListenerManager().addJobListener(new MyJogListener());
        scheduler.getListenerManager().addTriggerListener(new MyTriggerListener());

Quartz Configuration Reference

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

Configuration property



Enable cluster mode or not. If enabled make sure to set the appropriate cluster properties.



The frequency (in milliseconds) at which the scheduler instance checks-in with other instances of the cluster.



The type of store to use. When using the db store type configuration value make sure that you have the datasource configured. See Configuring your datasource for more information. To create Quartz tables, you can perform a schema migration via the Flyway extension using a SQL script matching your database picked from Quartz repository.

ram, jdbc-tx, jdbc-cmt, db


The name of the datasource to use. Optionally needed when using the db store type. If not specified, defaults to using the default datasource.


The prefix for quartz job store tables. Ignored if using a ram store.



The name of the Quartz instance.



The size of scheduler thread pool. This will initialize the number of worker threads in the pool.



Thread priority of worker threads in the pool.



Scheduler can be started in different modes: normal, forced or halted. By default, the scheduler is not started unless a io.quarkus.scheduler.Scheduled business method is found. If set to "forced", scheduler will be started even if no scheduled business methods are found. This is necessary for "pure" programmatic scheduling. Additionally, setting it to "halted" will behave just like forced mode but the scheduler will not start triggering jobs until an explicit start is called from the main scheduler. This is useful to programmatically register listeners before scheduler starts performing some work.

normal, forced, halted


Trigger listeners



Class name for the configuration.




Job listeners



Class name for the configuration.



The properties passed to the class.





Class name for the configuration.



The properties passed to the class.