Introduction

The Job Service is a distributed job scheduling and execution service built on top of the Quartz Scheduler framework. It provides a REST interface for managing scheduled jobs and triggers, allowing you to define when and how frequently jobs should be executed. The service is designed to operate in a clustered environment where multiple instances work together to execute jobs in a load-balanced manner. All job scheduling data and synchronization is managed through a shared database, ensuring consistency across all service instances in the cluster.

Writing jobs

The job implementations must be provided in terms of Java classes that implement the interface org.quartz.Job. All JobService instances in a cluster will compete for executing all pending jobs. Hence, the Java classes that implement the jobs must be available on all JobService instances in the cluster.

When a job implementation must not be executed concurrently, annotate the job class with @DisallowConcurrentExecution. This ensures that the job is never executed by more than one Job Service instance in the cluster at the same time.

The JAR file containing the job implementation classes must be a Spring Boot starter. It must be added to the classpath of the job service by placing it in the directory defined by the parameter loader.path used to start the service. The starter must register the required Spring beans for the jobs. The test-jobs module in the source code repository of the Job Service can be used as an example for such a starter.

The following Maven dependencies are required to implement jobs:

<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
    <groupId>de.eitco.commons</groupId>
    <artifactId>cmn-job-service-common</artifactId>
</dependency>

Annotation based job registration

The easiest way to register a job is to use annotations. By adding @JobDetails to your job implementation, the JobDetail will be automatically created. You can also use @CronTrigger or @SimpleTrigger to automatically create triggers for the job.

Example for an annotated Job implementation
@JobDetails(name = "AnnotatedTestJob", group = "test", description = "Test job for annotation-based job execution") (1)
@CronTrigger(name = "AnnotatedTestJobTrigger1", group = "test", cronExpression = "0/5 * * * * ?") (2)
@CronTrigger(name = "AnnotatedTestJobTrigger2", group = "test", cronExpression = "0 0 0 * * ?", jobConfigurationKey = "test-jobs.test-job-1.config") (3)
public class AnnotatedTestJob implements Job {

    private static final Logger LOGGER = Logger.getLogger(AnnotatedTestJob.class);

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {

        LOGGER.info(() -> "Executing AnnotatedTestJob");
    }
}
1 The @JobDetails annotation defines the name, group, and description of the job.
2 The @CronTrigger annotation defines a cron trigger for the job.
3 A second cron trigger with job configuration data loaded from the Job Service’s configuration at the specified key.

To make the JobService aware of the annotated job, you need to register it as a Spring bean:

Example for registering an annotated Job as a bean
@Bean
public Job annotatedTestJob() {
    return new AnnotatedTestJob();
}

Alternatively, if you use classpath scanning, you can simply add @Component to your job class.

Programmatic job registration

If you need more control over the job registration or prefer to separate job implementations from their configuration, you can register jobs programmatically. This approach involves three steps:

  1. Implement the org.quartz.Job interface.

  2. Register a JobDetail as a Spring bean, preferably using JobDetailFactoryBean.

  3. Register a trigger for the job, preferably using CronTriggerFactoryBean or SimpleTriggerFactoryBean.

Implementing the Job

The job implementation is a Java class that implements the org.quartz.Job interface.

Example for a Job implementation
public class SampleJob implements Job {

    // just an arbitrary object to test if Autowired dependency injection works for Jobs
    @Autowired (1)
    private ApplicationContext context;

    public void execute(JobExecutionContext context) throws JobExecutionException { (2)

        System.out.println("SampleJob");
    }
}
1 Dependency injection will work properly when the job class is instantiated via a JobDetailFactoryBean.
2 Implement your logic within the execute method.
You can pass configuration data from the trigger to the job using the job data map which can be set in the trigger factory bean and then accessed using the JobExecutionContext.

Note that a new instance of the job class is created for each execution using its default constructor.

Registering the JobDetail

To make a job available to the scheduler, it must be registered as a JobDetail. While Quartz requires JobDetail objects, it is highly recommended to use Spring’s JobDetailFactoryBean. This ensures that Spring handles the instantiation of the job class, allowing for dependency injection (e.g., via @Autowired).

The name of the JobDetail will be the name of the Spring bean (unless explicitly specified otherwise), and the default group is DEFAULT.

Example for a JobDetail bean definition
@Bean
public JobDetailFactoryBean alienCheckJobDetail() {
    JobDetailFactoryBean jobDetailFactory = new JobDetailFactoryBean();
    jobDetailFactory.setJobClass(SampleJob.class); (1)
    jobDetailFactory.setDescription("Invoke Alien check ...");
    jobDetailFactory.setDurability(true); (2)
    return jobDetailFactory;
}
1 Specify the job implementation class.
2 Setting durability to true ensures the job detail is not deleted when it’s no longer referenced by any trigger.

Registering the Trigger

Once the JobDetail is registered, you can define one or more triggers for it. For programmatic registration, it is recommended to use Spring’s CronTriggerFactoryBean or SimpleTriggerFactoryBean.

Example for a CronTrigger bean definition
@Bean
public CronTriggerFactoryBean alienCheckTrigger(JobDetail alienCheckJobDetail) {
    CronTriggerFactoryBean factoryBean = new CronTriggerFactoryBean();
    factoryBean.setJobDetail(alienCheckJobDetail); (1)
    factoryBean.setCronExpression("0 0 0 * * ?"); (2)
    factoryBean.setDescription("Trigger for Alien check job");
    return factoryBean;
}
1 Reference the JobDetail bean. Spring will automatically inject the JobDetail created by the JobDetailFactoryBean.
2 Define the schedule using a cron expression.

Clustering

The job service is designed to be used in cluster mode: that is several JobService instances care jointly about executing the scheduled jobs and strive to achieve a perfect load balancing between all JobService instances in the cluster. All JobService instances can change the triggers that define which jobs are to be triggered and when these jobs are to be triggered.

The synchronization of the JobService instances is done by using a common database instance and leverages row-locks as synchronization mechanism. Hence all JobService instances must have access to very same database tables.

The JobService is not multi-tenant capable! The JobService will currently fail to start if there are several tenants configured.

Cluster mode is enabled by default via the following default properties in the application.yaml

Default JobService Configuration
Unresolved directive in clustering.adoc - include::../../../../packaging/configuration/src/main/resource-templates/application.yaml[tag=cluster-config]

Disable job execution on individual JobService instances

By default, all JobService instances will jointly take care of executing jobs and strive to achieve a perfect load balancing between all JobService instances in the cluster. The execution of job’s on individual JobService instances can be disabled, such that these instances will not participate in executing jobs. This can be achieved by setting the configuration option spring.quartz.standbyOnlyScheduler.

JobService Configuration
job-service:
  standbyOnlyScheduler: true

Miscellaneous

Immediately triggering a job

A job can be immediately triggered by scheduling a simple trigger with StartTime set to now. A simple trigger will be automatically deleted by the scheduler if all executions have been executed. If you want to check the progress of the job you could define the job to be repeated in the far future.

Example for immediately triggering a job
    public void simpleTriggerTest() throws InterruptedException {
        SchedulerResourceClient client = newClient();

        final JobKeyModel jobKey = new JobKeyModel();
        jobKey.setName("alienCheckJobDetail");
        //jobKey.setGroup("DEFAULT"); (1)

        TriggerKeyModel triggerKey = new TriggerKeyModel();
        triggerKey.setName("simpleTriggerTest");
        //triggerKey.setGroup("DEFAULT"); (1)

        SimpleTriggerModel model = new SimpleTriggerModel();
        model.setKey(triggerKey);
        model.setJobKey(jobKey);
        model.setStartTime(ZonedDateTime.now());

        //model.setEndTime( ZonedDateTime.now().plusYears(11) ); (2)
        //model.setRepeatInterval(10 * 365 * 24 * 60 * 60 * 1000L ); // 10 years (2)
        //model.setRepeatCount(1); (2)

        client.scheduleSimpleTrigger(model);

        // wait some time (2)
        // Thread.sleep(60 * 1000); // 60 seconds (2)

        // fetch all the triggerDetails and see what has happened to our job... (2)
        //final List<TriggerModel> triggerDetails = client.getTriggerDetails(); (2)
        //... see (2)

        // finally delete the trigger again (2)
        //client.unscheduleJob(triggerKey); (2)
    }
1 The group names need not be specified at all for the default group
2 If you want to check the creation and progress of the job you could define the job to be repeated at least once in the far future. The commented lines suggest how this could implemented…​

Configuration Options

There are three sets of config options that can both be configured via the spring configuration files:

  • spring supported options, see: org.springframework.boot.autoconfigure.quartz.QuartzProperties. In the yaml file these properties are rooted at spring.quartz.

  • standard quartz options, that are defined by the quartz implementation and exist independently form the spring support. See: http://www.quartz-scheduler.org/documentation/quartz-2.3.0/configuration/. In the yaml file these properties are rooted at spring.quartz.properties.

  • the additional JobService specific option standbyOnlyScheduler. In the yaml file this property is located at spring.quartz.standbyOnlyScheduler.

Logging of QUARTZ’s activities

QUARTZ has an event based extension interface that allows to get notified about activities in the local scheduler instance and there are two extensions that just log these events to a log file. This kind of logging comes in hand during development and can be enabled via these config options:

JobService Configuration
spring:
  quartz:
    properties:
      org:
        quartz:
          plugin:
            jobHistory:
              class: "org.quartz.plugins.history.LoggingJobHistoryPlugin"
            triggHistory:
              class: "org.quartz.plugins.history.LoggingTriggerHistoryPlugin"

References

Release Notes

Release notes 14.0.2-SNAPSHOT (November 06, 2026)

Details

New Features

Changes

Bug Fixes

Interface breaking changes

Configuration changes

Manual migration steps

Release notes 14.0.1 (October 13, 2026)

Details

New Features

Changes

  • ECR-1930: Switch to Fabric8 Kubernetes discovery client.

Bug Fixes

Interface breaking changes

Configuration changes

Manual migration steps

Release notes 14.0.0 (September 29, 2026)

Details

New Features

Changes

Bug Fixes

Interface breaking changes

Configuration changes

Manual migration steps

  • UPG-102: Restructured the module layout

    All backend modules now live below backend, the delivery artifacts below packaging. This changes Maven coordinates and Java packages (see migration guide below). The groupId de.eitco.commons is unchanged.

    Migration guide: delivery artifacts (packaging)

    Artifact up to 13.0.1 Artifact from 14.0.0

    cmn-job-service-http-client-spring-boot-starter

    cmn-job-service-sdk-http-spring

    cmn-job-service-http-client-apache

    cmn-job-service-sdk-http-apache

    cmn-job-service-embedded-spring-boot-starter

    cmn-job-service-sdk-embedded

    cmn-job-service-standalone (and cmn-job-service-configuration)

    cmn-job-service

    The sdk artifacts bring in all required client modules transitively; do not depend on the modules below directly. If you did, note the renames: cmn-job-service-common → cmn-job-service-api, cmn-job-service-http-client → cmn-job-service-client-http, cmn-job-service-core and cmn-job-service-http → cmn-job-service-implementation.

    Migration guide: model classes (cmn-job-service-model)

    • The artifactId of the model module is unchanged, its classes (CronTriggerModel, JobDetailModel, JobKeyModel, KeyModel, RunningJobModel, SchedulerState, SimpleTriggerModel, TriggerKeyModel, TriggerModel) keep the package de.eitco.commons.job.service.common. No import changes are required for them.

    • JobServiceModel has been removed.

    • The module no longer depends on cmn-commons-lang. If your code relied on it transitively, declare it explicitly.

    Migration guide: Java packages

    Up to 13.0.1 From 14.0.0

    de.eitco.commons.job.service.common.SchedulerResource

    de.eitco.commons.job.service.api.SchedulerResource

    de.eitco.commons.job.service.common.InfoResource

    de.eitco.commons.job.service.api.InfoResource

    de.eitco.commons.job.service.common.JobServiceConstants

    de.eitco.commons.job.service.api.JobServiceConstants

    generated clients in de.eitco.commons.job.service.common

    generated clients in de.eitco.commons.job.service.api

    de.eitco.commons.job.service.HttpClientConfiguration

    de.eitco.commons.job.service.api.HttpClientConfiguration

    de.eitco.commons.job.service.client.starter.JobServiceClientAutoConfiguration

    de.eitco.commons.job.service.http.spring.JobServiceClientAutoConfiguration

    de.eitco.commons.job.service.client.starter.JobServiceEmbeddedClientAutoConfiguration

    de.eitco.commons.job.service.embedded.JobServiceEmbeddedClientAutoConfiguration

    de.eitco.commons.job.service.core., de.eitco.commons.job.service.http.

    de.eitco.commons.job.service.implementation.*

    Checklist

    1. Replace the artifactIds in your pom.xml according to the first table.

    2. Adjust imports of the moved classes; the model classes need no change.

    3. Update @Import, spring.autoconfigure.exclude or similar references to the renamed auto-configuration classes.

    4. If you deploy the service, use the artifact cmn-job-service instead of cmn-job-service-standalone.