Spring Boot — Quartz Scheduler Example : Building an Email Scheduling app | Code Factory

Code Factory
7 min readJun 11, 2020

--

Donate : Link

WordPress Blog : Link

Quartz is an open source Java library for scheduling Jobs. It has a very rich set of features including but not limited to persistent Jobs, transactions, and clustering.

You can schedule Jobs to be executed at a certain time of day, or periodically at a certain interval, and much more. Quartz provides a fluent API for creating jobs and scheduling them.

In this article, you’ll learn how to schedule Jobs in spring boot using Quartz Scheduler by building a simple Email Scheduling application. The application will have a Rest API that allows clients to schedule Emails at a later time.

We’ll use MySQL to persist all the jobs and other job-related data.

  • Open http://start.spring.io
  • Enter spring-boot-quartz-demo in the Artifact field.
  • Add Web, JPA, MySQL, Quartz, and Mail in the dependencies section.
  • Click Generate to generate and download the project.

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>spring-boot-quartz-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-quartz-demo</name>
<description>Demo project for Spring Boot Quartz Demo</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

application.properties

# Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
spring.datasource.url = jdbc:mysql://localhost:3306/quartz_demo?useSSL=false
spring.datasource.username = root
spring.datasource.password = root
# QuartzProperties
spring.quartz.job-store-type = jdbc
#spring.quartz.job-store-type = memory # to store in memory
spring.quartz.properties.org.quartz.threadPool.threadCount = 5
# MailProperties
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username=EMAIL@GMAIL.COM
spring.mail.password=
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true

You’ll need to create a MySQL database named quartz_demo. Also, don’t forget to change the spring.datasource.username and spring.datasource.password properties as per your MySQL installation.

We’ll be using Gmail’s SMTP server for sending emails. Please add your password in the spring.mail.password property. You may also pass this property at runtime as command line argument or set it in the environment variable.

Note that, Gmail’s SMTP access is disabled by default. To allow this app to send emails using your Gmail account -

All the quartz specific properties are prefixed with spring.quartz. You can refer to the complete set of configurations supported by Quartz in its official documentation. To directly set configurations for Quartz scheduler, you can use the format spring.quartz.properties.<quartz_configuration_name>=<value>.

Creating Quartz Tables

Since we have configured Quartz to store Jobs in the database, we’ll need to create the tables that Quartz uses to store Jobs and other job-related meta-data.

Please download the following SQL script and run it in your MySQL database to create all the Quartz specific tables.

After downloading the above SQL script, login to MySQL and run the script like this -

mysql> source <PATH_TO_QUARTZ_TABLES.sql>

Overview of Quartz Scheduler’s APIs and Terminologies

1. Scheduler

The Primary API for scheduling, unscheduling, adding, and removing Jobs.

2. Job

The interface to be implemented by classes that represent a ‘job’ in Quartz. It has a single method called execute() where you write the work that needs to be performed by the Job.

3. JobDetail

A JobDetail represents an instance of a Job. It also contains additional data in the form of a JobDataMap that is passed to the Job when it is executed.

Every JobDetail is identified by a JobKey that consists of a name and a group. The name must be unique within a group.

4. Trigger

A Trigger, as the name suggests, defines the schedule at which a given Job will be executed. A Job can have many Triggers, but a Trigger can only be associated with one Job.

Every Trigger is identified by a TriggerKey that comprises of a name and a group. The name must be unique within a group.

Just like JobDetails, Triggers can also send parameters/data to the Job.

5. JobBuilder

JobBuilder is a fluent builder-style API to construct JobDetail instances.

6. TriggerBuilder

TriggerBuilder is used to instantiate Triggers.

Creating a REST API to schedule Email Jobs dynamically in Quartz

All right! Let’s now create a REST API to schedule email Jobs in Quartz dynamically. All the Jobs will be persisted in the database and executed at the specified schedule.

Before writing the API, Let’s create the DTO classes that will be used as request and response payloads for the scheduleEmail API -

ScheduleEmailRequest.java

package com.example.payload;import java.time.LocalDateTime;
import java.time.ZoneId;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotEmpty;
import com.sun.istack.NotNull;/**
* @author code.factory
*
*/
public class ScheduleEmailRequest {
@Email
@NotEmpty
private String email;
@NotEmpty
private String subject;
@NotEmpty
private String body;
@NotNull
private LocalDateTime dateTime;
@NotNull
private ZoneId timeZone;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public LocalDateTime getDateTime() {
return dateTime;
}
public void setDateTime(LocalDateTime dateTime) {
this.dateTime = dateTime;
}
public ZoneId getTimeZone() {
return timeZone;
}
public void setTimeZone(ZoneId timeZone) {
this.timeZone = timeZone;
}
}

ScheduleEmailResponse.java

package com.example.payload;import com.fasterxml.jackson.annotation.JsonInclude;/**
* @author code.factory
*
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ScheduleEmailResponse {
private boolean success;
private String jobId;
private String jobGroup;
private String message;
public ScheduleEmailResponse(boolean success, String message) {
this.success = success;
this.message = message;
}
public ScheduleEmailResponse(boolean success, String jobId, String jobGroup, String message) {
this.success = success;
this.jobId = jobId;
this.jobGroup = jobGroup;
this.message = message;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public String getJobId() {
return jobId;
}
public void setJobId(String jobId) {
this.jobId = jobId;
}
public String getJobGroup() {
return jobGroup;
}
public void setJobGroup(String jobGroup) {
this.jobGroup = jobGroup;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}

ScheduleEmail Rest API

The following controller defines the /scheduleEmail REST API that schedules email Jobs in Quartz -

EmailJobSchedulerController.java

package com.example.controller;import java.time.ZonedDateTime;
import java.util.Date;
import java.util.UUID;
import javax.validation.Valid;import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.example.job.EmailJob;
import com.example.payload.ScheduleEmailRequest;
import com.example.payload.ScheduleEmailResponse;
/**
* @author code.factory
*
*/
@RestController
public class EmailJobSchedulerController {
private static final Logger logger = LoggerFactory.getLogger(EmailJobSchedulerController.class); @Autowired
private Scheduler scheduler;
@PostMapping("/scheduleEmail")
public ResponseEntity<ScheduleEmailResponse> scheduleEmail(@Valid @RequestBody ScheduleEmailRequest scheduleEmailRequest) {
try {
ZonedDateTime dateTime = ZonedDateTime.of(scheduleEmailRequest.getDateTime(), scheduleEmailRequest.getTimeZone());
if (dateTime.isBefore(ZonedDateTime.now())) {
ScheduleEmailResponse scheduleEmailResponse = new ScheduleEmailResponse(false, "dateTime must be after current time");
return ResponseEntity.badRequest().body(scheduleEmailResponse);
}
JobDetail jobDetail = buildJobDetail(scheduleEmailRequest);
Trigger trigger = buildJobTrigger(jobDetail, dateTime);
scheduler.scheduleJob(jobDetail, trigger);
ScheduleEmailResponse scheduleEmailResponse = new ScheduleEmailResponse(true, jobDetail.getKey().getName(),
jobDetail.getKey().getGroup(), "Email Scheduled Successfully!");
return ResponseEntity.ok(scheduleEmailResponse);
} catch (SchedulerException ex) {
logger.error("Error scheduling email", ex);
ScheduleEmailResponse scheduleEmailResponse = new ScheduleEmailResponse(false, "Error scheduling email. Please try later!");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(scheduleEmailResponse);
}
}
private JobDetail buildJobDetail(ScheduleEmailRequest scheduleEmailRequest) {
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("email", scheduleEmailRequest.getEmail());
jobDataMap.put("subject", scheduleEmailRequest.getSubject());
jobDataMap.put("body", scheduleEmailRequest.getBody());
return JobBuilder.newJob(EmailJob.class).withIdentity(UUID.randomUUID().toString(), "email-jobs")
.withDescription("Send Email Job").usingJobData(jobDataMap).storeDurably().build();
}
private Trigger buildJobTrigger(JobDetail jobDetail, ZonedDateTime startAt) {
return TriggerBuilder.newTrigger().forJob(jobDetail)
.withIdentity(jobDetail.getKey().getName(), "email-triggers").withDescription("Send Email Trigger")
.startAt(Date.from(startAt.toInstant()))
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withMisfireHandlingInstructionFireNow()).build();
}
}

Spring Boot has built-in support for Quartz. It automatically creates a Quartz Scheduler bean with the configuration that we supplied in the application.properties file. That’s why we could directly inject the Scheduler in the controller.

In the /scheduleEmail API,

  • We first validate the request body
  • Then, Build a JobDetail instance with a JobDataMap that contains the recipient email, subject, and body. The JobDetail that we create is of type EmailJob. We’ll define EmailJob in the next section.
  • Next, we Build a Trigger instance that defines when the Job should be executed.
  • Finally, we schedule the Job using scheduler.scheduleJob() API.

Creating the Quartz Job to sends emails

Let’s now define the Job that sends the actual emails. Spring Boot provides a wrapper around Quartz Scheduler’s Job interface called QuartzJobBean. This allows you to create Quartz Jobs as Spring beans where you can autowire other beans.

Let’s create our EmailJob by extending QuartzJobBean -

EmailJob.java

package com.example.job;import java.nio.charset.StandardCharsets;import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.mail.MailProperties;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;
/**
* @author code.factory
*
*/
@Component
public class EmailJob extends QuartzJobBean {
private static final Logger logger = LoggerFactory.getLogger(EmailJob.class); @Autowired
private JavaMailSender mailSender;
@Autowired
private MailProperties mailProperties;
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
logger.info("Executing Job with key {}", jobExecutionContext.getJobDetail().getKey());
JobDataMap jobDataMap = jobExecutionContext.getMergedJobDataMap();
String subject = jobDataMap.getString("subject");
String body = jobDataMap.getString("body");
String recipientEmail = jobDataMap.getString("email");
sendMail(mailProperties.getUsername(), recipientEmail, subject, body);
}
private void sendMail(String fromEmail, String toEmail, String subject, String body) {
try {
logger.info("Sending Email to {}", toEmail);
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper messageHelper = new MimeMessageHelper(message, StandardCharsets.UTF_8.toString());
messageHelper.setSubject(subject);
messageHelper.setText(body, true);
messageHelper.setFrom(fromEmail);
messageHelper.setTo(toEmail);
mailSender.send(message);
} catch (MessagingException ex) {
logger.error("Failed to send email to {}", toEmail);
}
}
}

Running the Application and Testing the API

It’s time to run the application and watch the live action. Open your terminal, go to the root directory of the project and type the following command to run it -

mvn spring-boot:run -Dspring.mail.password=<YOUR_SMTP_PASSWORD>

You don’t need to pass the spring.mail.password command line argument if you have already set the password in the application.properties file.

OR Run by using the main() method.

SpringBootQuartzDemoApplication.java

package com.example;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author code.factory
*
*/
@SpringBootApplication
public class SpringBootQuartzDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootQuartzDemoApplication.class, args);
}
}

The application will start on port 8080 by default. Let’s now schedule an email using the /scheduleEmail API -

And, Here I get the email at the specified time

--

--