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

  • 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.
<?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>
# 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
mysql> source <PATH_TO_QUARTZ_TABLES.sql>

1. Scheduler

2. Job

3. JobDetail

4. Trigger

5. JobBuilder

6. TriggerBuilder

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;
}
}
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

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();
}
}
  • 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

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

mvn spring-boot:run -Dspring.mail.password=<YOUR_SMTP_PASSWORD>
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);
}
}

--

--

--

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

A Real Time E-mail Tracking Solution with AWS Services

Using Cookies to Create Realistic Lighting

How to Buy Land in MetaCity

How to Calculate Your Streaming Royalties with Your Own .CSV File

Release v3@alpha.9 🎉

Different between 8085 and 8086 and their relationship with core processors.

Vim and tmux awesomeness for Rails development

A Guide to Groovy DSL: Configuration

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Code Factory

Code Factory

More from Medium

Spring Cloud Netflix

Java Couchbase |Mysql | Oracle Database Connect And Data Logger!

What is SPRING BOOT ?

From zero to hero — Deploy a scalable Java app on AWS App Runner ..