Learn Quarkus - Batch Jobs & Scheduling
Episode 11 of 24

Learn Quarkus - Batch Jobs & Scheduling

This episode covers scheduled jobs in Quarkus: the Quarkus Scheduler with @Scheduled, building batch processing, task executor and concurrency control configuration, and job monitoring and retry handling.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Many backend tasks happen without a user request: sending emails, cleaning up expired files, pulling data from external APIs, or generating daily reports. These kinds of jobs run on a schedule in the background.

Episode 11 covers the Quarkus Scheduler for scheduled tasks, how to build reliable batch processing, task executor and concurrency control configuration, as well as job monitoring and retry strategies.

The Quarkus Scheduler for Scheduled Tasks

Adding the Extension

Adding the scheduler extension
./mvnw quarkus:add-extension -Dextensions=scheduler

The command ./mvnw quarkus:add-extension -Dextensions=scheduler adds the scheduler dependency to your project.

The @Scheduled Annotation

Methods marked @Scheduled are run automatically by the scheduler:

JavaJob with an interval
import io.quarkus.scheduler.Scheduled;
import jakarta.enterprise.context.ApplicationScoped;
 
@ApplicationScoped
public class ReportJob {
 
    @Scheduled(every = "60s")
    void buatLaporan() {
        System.out.println("Membuat laporan terjadwal");
    }
}

@Scheduled(every = "60s") runs the method every 60 seconds. The duration format uses ISO 8601 — 10s, 1m, 1h, or combinations.

Cron Expressions

For more precise schedules, use cron:

JavaJob with cron
import io.quarkus.scheduler.Scheduled;
 
@Scheduled(cron = "0 30 2 * * ?")
void backupMalam() {
    // runs every day at 02:30
}

The expression "0 30 2 * * ?" means second 0, minute 30, hour 2, every day. Quarkus cron uses the Quartz format — six or seven fields including seconds.

Building Batch Processing with Quarkus

Batch processing should process data in small chunks so it doesn't exhaust memory. The pagination pattern is very useful:

JavaBatch with pagination
import io.quarkus.hibernate.orm.panache.PanacheQuery;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
 
@ApplicationScoped
public class CleanupJob {
 
    @Inject
    SessionRepository sessionRepository;
 
    @Scheduled(cron = "0 0 4 * * ?")
    @Transactional
    void bersihkanSessionKadaluarsa() {
        int halaman = 0;
        int batchSize = 500;
        List<Session> batch;
        do {
            PanacheQuery<Session> query =
                sessionRepository.find("expiredAt < ?1", now());
            batch = query.range(halaman * batchSize,
                (halaman + 1) * batchSize - 1).list();
            sessionRepository.delete(batch);
            halaman++;
        } while (!batch.isEmpty());
    }
}

The query.range(start, end) pattern processes data in chunks of 500 rows. @Transactional on the batch method makes one transaction per execution — if it fails halfway, the whole batch is rolled back.

Task Executor and Concurrency Control Configuration

The Scheduler Executor

The Quarkus Scheduler runs on top of an executor:

Scheduler configuration
quarkus.scheduler.threads=5
quarkus.scheduler.overdue-grace-period=30S

quarkus.scheduler.threads=5 determines the number of threads available for jobs. The default of 10 is enough for most cases.

Preventing Execution Overlap

A job that runs longer than its interval can pile up. Control it by skipping while still running:

Preventing overlap
quarkus.scheduler.concurrent-execution.skip-on-concurrent=true

With skip-on-concurrent=true, if the previous execution hasn't finished, the next one is skipped. An alternative in code: use an AtomicBoolean with compareAndSet(false, true) to lock the execution and release the lock in a finally block.

Job Monitoring and Retry Handling

Retry with SmallRye Fault Tolerance

A job that calls an external service can fail temporarily. Use retry:

JavaRetrying a failed job
import io.quarkus.scheduler.Scheduled;
import org.eclipse.microprofile.faulttolerance.Retry;
import jakarta.enterprise.context.ApplicationScoped;
import java.io.IOException;
 
@ApplicationScoped
public class SyncJob {
 
    @Scheduled(every = "5m")
    @Retry(maxRetries = 3, delay = 2000, retryOn = {IOException.class})
    void sinkronisasiExternal() {
        panggilApiEksternal();
    }
}

@Retry(maxRetries = 3, delay = 2000) retries up to three times with a 2-second pause when an IOException occurs.

Monitoring Executions

The Quarkus Scheduler exposes execution metrics at /q/metrics — check them with curl http://localhost:8080/q/metrics | grep scheduler. The metrics cover the number of successful, failed, and overdue executions. For better visibility, log the start and end of every job: clear logs help you trace problem jobs in production.

Wrap-Up

Episode 11 makes your application work on its own: understanding the Quarkus Scheduler with @Scheduled and cron, building batch processing with pagination and transactions, controlling concurrency with executors and atomic flags, and handling retries and job monitoring.

Key takeaways:

  • @Scheduled(every = "60s") for intervals, @Scheduled(cron = "...") for precise schedules.
  • Batches are processed in chunks to save memory.
  • quarkus.scheduler.concurrent-execution.skip-on-concurrent prevents overlap.
  • AtomicBoolean gives you control over concurrent execution in code.
  • @Retry from SmallRye Fault Tolerance handles temporary failures.
  • Scheduler metrics are available at /q/metrics.

In episode 12 we'll cover basic security and auth — an introduction to Quarkus Security and HTTP authentication, role-based access control and identity stores, authentication mechanism configuration, as well as CSRF, CORS, and secure headers protection.

Learn Quarkus - Batch Jobs & Scheduling | Learn Quarkus