In this guide, we will learn how to:
- setup JobRunr
- learn how to enqueue and schedule a job in vanilla Java or your favorite web framework using the
JobRequest
/JobRequestHandler
pattern. - monitor your jobs using the built-in dashboard
What is JobRunr
Introduction
JobRunr is a library that we can embed in our application and which allows us to schedule background jobs using a Java 8 lambda. We can use any existing method of our Spring services to create a job without the need to implement an interface. A job can be a short or long-running process, and it will be automatically offloaded to a background thread so that the current web request is not blocked.
To do its job (pun intended 😅), JobRunr can not only use a Java 8 lambda but it can also use the Command
/ CommandHandler
pattern, in this case an implementation of a JobRequest
and a JobRequestHandler
. It serializes the JobRequest
(consider it as a DTO) as JSON, and stores it into either a relational database or a NoSQL data store.
Creating jobs using the JobRequest and JobRequestHandler pattern
When we want to enqueue or schedule a job by means of a JobRequest
we will need to create two classes, one implementing the JobRequest
interface and one implementing the JobRequestHandler
interface:
We can now create a job as follows:
Then JobRunr will analyze this lambda and create a JSON representation of it which can be saved in the SQL or NoSQL database and then be processed even on another server:
Setup
Maven dependency
Now that we know how JobRunr works, let’s jump straight to the Java code. But before that, we need to have the following Maven dependency declared in our pom.xml
file:
<dependency>
<groupId>org.jobrunr</groupId>
<artifactId>jobrunr</artifactId>
<version>7.0.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version> <!-- use latest version -->
</dependency>
As JobRunr also needs a library for JSON handling, we also include Jackson as a dependency.
<dependency>
<groupId>org.jobrunr</groupId>
<artifactId>jobrunr-spring-boot-3-starter</artifactId>
<version>7.0.0</version>
</dependency>
JobRunr also needs a library for JSON handling, but as Spring Boot by default comes with Jackson support this is already covered.
<dependency>
<groupId>org.jobrunr</groupId>
<artifactId>quarkus-jobrunr</artifactId>
<version>7.0.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version> <!-- use latest version -->
</dependency>
JobRunr also needs a library for JSON handling and just like Quarkus, JobRunr both supports Jackson and JSON-B.
<dependency>
<groupId>org.jobrunr</groupId>
<artifactId>jobrunr-micronaut-feature</artifactId>
<version>7.0.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version> <!-- use latest version -->
</dependency>
JobRunr also needs a library for JSON handling - for Micronaut we recommend Jackson.
JobRunr Configuration
Before we jump straight to how to create background jobs, we need to initialize JobRunr. In this guide, we will enable both the BackgroundJobServer
so that jobs get processed and the dashboard.
Configuring JobRunr using the Fluent API is really easy, we only need a bit of code to configure JobRunr:
public class Main {
public static void main(String[] args) throws Exception {
JobRunr
.configure()
.useStorageProvider(new InMemoryStorageProvider())
.useDashboard()
.useBackgroundJobServer()
.initialize();
}
}
As we’re using the jobrunr-spring-boot-3-starter
dependency, this is easy. We only need to add some properties to the application.properties
:
org.jobrunr.background-job-server.enabled=true
org.jobrunr.dashboard.enabled=true
As we’re using the quarkus-jobrunr
extension, this is easy. We only need to add some properties to the application.properties
:
quarkus.jobrunr.background-job-server.enabled=true
quarkus.jobrunr.dashboard.enabled=true
As we’re using the jobrunr-micronaut-feature
, this is easy. We only need to add some properties to the application.yml
:
jobrunr:
background-job-server:
enabled: true
dashboard:
enabled: true
Enqueueing one-off jobs
Now, let’s find out how to create some fire-and-forget background jobs using JobRunr.
We can now start using JobRunr by means of the BackgroundJob
:
public class Main {
public static void main(String[] args) throws Exception {
// ...
BackgroundJobRequest.enqueue(new SysOutJobRequest("This is a background job!")); // you will also need a SysOutJobRequestHandler to run the actual job
}
}
When we want to create jobs, we’ll need to inject the JobRequestScheduler
so we can pass it an implementation of a JobRequest
, in this case a MyJobRequest
:
@RestController
public class JobController {
private final JobRequestScheduler jobRequestScheduler;
public JobController(JobRequestScheduler jobRequestScheduler) {
this.jobRequestScheduler = jobRequestScheduler;
}
@GetMapping("/enqueue-example-job")
public String enqueueExampleJob(@RequestParam(value = "name", defaultValue = "World") String name) {
final JobId enqueuedJobId = jobRequestScheduler.enqueue(new MyJobRequest(name));
return "Job Request Enqueued: " + enqueuedJobId;
}
}
When we want to create jobs, we’ll need to inject the JobRequestScheduler
so we can pass it an implementation of a JobRequest
, in this case a MyJobRequest
:
@Path("jobs")
@ApplicationScoped
public class JobResource {
@Inject
JobRequestScheduler jobRequestScheduler;
@GET
@Path("/simple-job")
@Produces(MediaType.TEXT_PLAIN)
public String simpleJob(@DefaultValue("Hello world") @QueryParam("value") String value) {
final JobId enqueuedJobId = jobRequestScheduler.enqueue(new MyJobRequest(value));
return "Job Request Enqueued: " + enqueuedJobId;
}
}
When we want to create jobs, we’ll need to inject the JobRequestScheduler
so we can pass it an implementation of a JobRequest
, in this case a MyJobRequest
:
@Controller("/jobs")
public class JobController {
@Inject
private JobRequestScheduler jobRequestScheduler;
@Get("/simple-job")
@Produces(MediaType.TEXT_PLAIN)
public String simpleJob(@QueryValue(value = "value", defaultValue = "Hello world") String value) {
final JobId enqueuedJobId = jobRequestScheduler.enqueue(new MyJobRequest(value));
return "Job Request Enqueued: " + enqueuedJobId;
}
}
Scheduling jobs
We can also schedule jobs in the future using the schedule method:
public class Main {
public static void main(String[] args) throws Exception {
// ...
BackgroundJob.schedule(LocalDateTime.now().plusHours(5), new SysOutJobRequest("This is a background job!"));
}
}
@RestController
public class JobController {
// ...
@GetMapping("/schedule-example-job")
public String scheduleExampleJob(
@RequestParam(value = "name", defaultValue = "World") String name,
@RequestParam(value = "when", defaultValue = "PT3H") String when) {
final JobId scheduledJobId = jobRequestScheduler.schedule(now().plus(Duration.parse(when)), new MyJobRequest(name));
return "Job Request Scheduled: " + scheduledJobId.toString();
}
}
@Path("jobs")
@ApplicationScoped
public class JobResource {
// ...
@GET
@Path("/schedule-simple-job")
@Produces(MediaType.TEXT_PLAIN)
public String scheduleSimpleJob(
@DefaultValue("Hello world") @QueryParam("value") String value,
@DefaultValue("PT3H") @QueryParam("when") String when) {
final JobId scheduledJobId = jobRequestScheduler.schedule(now().plus(Duration.parse(when)), new MyJobRequest(value));
return "Job Request Scheduled: " + scheduledJobId.toString();
}
}
@Controller("/jobs")
public class JobController {
// ...
@Get("/schedule-simple-job")
@Produces(MediaType.TEXT_PLAIN)
public String scheduleSimpleJob(
@QueryValue(value = "value", defaultValue = "Hello world") String value,
@QueryValue(value = "when", defaultValue = "PT3H") String when) {
final JobId scheduledJobId = jobRequestScheduler.schedule(now().plus(Duration.parse(when)), new MyJobRequest(value));
return "Job Request Scheduled: " + scheduledJobId.toString();
}
}
Monitoring jobs using the built-in dashboard
JobRunr comes with a built-in dashboard that allows us to monitor our jobs. We can find it at http://localhost:8000 and inspect all the jobs, including all recurring jobs and an estimation of how long it will take until all the enqueued jobs are processed:
Bad things can happen, for example, an SSL certificate expired, or a disk is full. JobRunr, by default, will reschedule the background job with an exponential back-off policy. If the background job continues to fail ten times, only then will it go to the Failed state. You can then decide to re-queue the failed job when the root cause has been solved.
All of this is visible in the dashboard, including each retry with the exact error message and the complete stack trace of why a job failed:
Conclusion
In this guide, we’ve learned how to effortlessly set up and use JobRunr to create and schedule jobs using a JobRequest
and JobRequestHandler
and we also learned how to monitor jobs with its user-friendly dashboard.