<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Spring Builders: Donatavict</title>
    <description>The latest articles on Spring Builders by Donatavict (@donatavict).</description>
    <link>https://springbuilders.dev/donatavict</link>
    <image>
      <url>https://springbuilders.dev/images/IOxJZAeYZ6mQr19A9AxFwIhFpI7qVoceaMGuAzR5QvM/rs:fill:90:90/g:sm/mb:500000/ar:1/aHR0cHM6Ly9zcHJp/bmdidWlsZGVycy5k/ZXYvdXBsb2Fkcy91/c2VyL3Byb2ZpbGVf/aW1hZ2UvNDExL2Q2/ZmUzY2UwLWY4YmQt/NDZkMi1hNjkwLWM4/YzE4YTVlZThjYS5w/bmc</url>
      <title>Spring Builders: Donatavict</title>
      <link>https://springbuilders.dev/donatavict</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://springbuilders.dev/feed/donatavict"/>
    <language>en</language>
    <item>
      <title>Task schedulers in Java: modern alternatives to Quartz Scheduler</title>
      <dc:creator>Donatavict</dc:creator>
      <pubDate>Fri, 08 Nov 2024 14:05:07 +0000</pubDate>
      <link>https://springbuilders.dev/donatavict/task-schedulers-in-java-modern-alternatives-to-quartz-scheduler-ogc</link>
      <guid>https://springbuilders.dev/donatavict/task-schedulers-in-java-modern-alternatives-to-quartz-scheduler-ogc</guid>
      <description>&lt;p&gt;Quartz is often considered the standard job scheduling library in Java, which can lead developers to overlook more modern alternatives.&lt;/p&gt;

&lt;p&gt;For a long time, Quartz, also known as Quartz Scheduler, was the only viable open source task scheduler in Java. In fact, the official Quartz documentation still suggests that there is &lt;a href="https://www.quartz-scheduler.org/documentation/2.3.1-SNAPSHOT/faq.html"&gt;no real alternative&lt;/a&gt;. In this article, we will list a few different &lt;strong&gt;Quartz Scheduler alternatives that offer a similar set of features while being easier and more enjoyable to use.&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  The Quartz Scheduler: how good is it really?
&lt;/h1&gt;

&lt;p&gt;Similar to its alternative, the &lt;strong&gt;Quartz Scheduler can be integrated into almost any Java application to schedule and process any task implemented in Java.&lt;/strong&gt; In its decades of existence, Quartz has evolved into a feature-rich framework for persisting task states, automatically retrying tasks on failure, defining complex schedules, prioritizing tasks, achieving distributed processing, and more.&lt;/p&gt;

&lt;p&gt;The fact that Quartz has been around for so long has two major advantages: &lt;strong&gt;the library is battle-tested and has a large community&lt;/strong&gt;, which makes it easier to find help. All these elements together make it the most popular job scheduling library in Java. Despite all these strengths, &lt;strong&gt;choosing Quartz is no longer so obvious due to some glaring issues.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations of Quartz
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Quartz is showing its age
&lt;/h3&gt;

&lt;p&gt;The Quartz scheduler has several issues that make it less of an obvious choice, especially when &lt;strong&gt;there are several really good alternatives.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The age of Quartz, while an advantage, is also a disadvantage, as the &lt;strong&gt;Quartz API and architecture are also getting older&lt;/strong&gt;. The Quartz API can be considered verbose by today’s standards. This may not seem like a big deal, but it’s important for a library or language to remain attractive to the next generation of developers. Reduced verbosity is one of the main factors contributing to the popularity of Kotlin, and Java itself is moving toward &lt;a href="https://www.infoq.com/news/2024/05/jep477-implicit-classes-main/"&gt;becoming more beginner-friendly&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Another place where Quartz shows its age is in its architecture. When using an RDBMS, &lt;strong&gt;the scheduler requires the creation of more than 10 tables&lt;/strong&gt;, which is probably overkill for most applications where the actual business logic requires fewer tables.&lt;/p&gt;

&lt;p&gt;We have written an article that &lt;a href="https://www.jobrunr.io/en/blog/2023-02-20-moving-from-quartz-scheduler-to-jobrunr/"&gt;compares Quartz to a more modern solution, namely JobRunr&lt;/a&gt;. This article &lt;strong&gt;gives a feel for how complex and verbose Quartz is to use&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performs worse than modern alternatives
&lt;/h3&gt;

&lt;p&gt;When compared to more modern alternatives, &lt;strong&gt;Quartz is reported as having significantly lower performance&lt;/strong&gt;. This performance issue may be caused by the use of row level lock and the complexity of the configuration making the Quartz Scheduler harder to fine-tune.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lack of built-in monitoring
&lt;/h3&gt;

&lt;p&gt;Quartz Scheduler &lt;strong&gt;lacks an intuitive dashboard or UI for tracking and monitoring jobs&lt;/strong&gt;. It also doesn’t provide a way to interface with external monitoring tools like &lt;a href="https://grafana.com/"&gt;Grafana&lt;/a&gt; or &lt;a href="https://www.jaegertracing.io/"&gt;Jaeger&lt;/a&gt;. Quartz users cannot actively monitor the system to proactively address issues such as job failures.&lt;/p&gt;

&lt;h3&gt;
  
  
  Distributed scheduling is opt-in
&lt;/h3&gt;

&lt;p&gt;Quartz has the ability to handle the tasks in a distributed fashion, but this feature is opt-in. With tools like &lt;a href="https://kubernetes.io/"&gt;Kubernetes&lt;/a&gt; now allowing a pod to spin up on demand, such a feature should not be an afterthought, but rather the default.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sporadic maintenance
&lt;/h3&gt;

&lt;p&gt;The last non-beta release of Quartz dates back to October 23, 2019. While development was on hold, &lt;strong&gt;the ecosystem around it didn’t stop evolving&lt;/strong&gt;. This hiatus led to a pile of unresolved issues. The users of the library are still suffering from the &lt;code&gt;javax&lt;/code&gt; to &lt;code&gt;jakarta&lt;/code&gt; namespace change, unless they use the latest release candidate, along with many bugs and security issues. They also &lt;strong&gt;cannot use newest Java features such as virtual threads&lt;/strong&gt;, that may benefit background job processing, for instance to increase throughput…&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;There are good news for Quartz users. the recent acquisition by IBM led to a spark in activity, with the community helping to fix all the major issues. It is still unclear what this acquisition means for the future of Quartz…&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Modern alternatives to Quartz Scheduler
&lt;/h1&gt;

&lt;p&gt;If you’re looking for a Quartz alternative for your Java application, there are &lt;strong&gt;several modern open source schedulers that offer similar features&lt;/strong&gt;, more developer-friendly APIs, and more robust support for distributed and cloud-based environments.&lt;/p&gt;

&lt;p&gt;We distinguish between two types of schedulers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The first class consists of Java job scheduling libraries. Similar to Quartz, &lt;strong&gt;they can be included in any Java application&lt;/strong&gt;. They live inside your application.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The second class are workflow engines, which target a broader audience. These are &lt;strong&gt;standalone services that your application will need to communicate with.&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Alternatives to Quartz in Java
&lt;/h2&gt;

&lt;p&gt;There are two great alternatives to Quartz for scheduling jobs in Java, namely JobRunr and db-scheduler. They can be added as a dependency to any Java application to &lt;strong&gt;provide the needed features for distributed background task processing and persisted task scheduling&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;These libraries keep the number of third-party dependencies to a minimum, providing better security insurance. They are available as standard Java jars on Maven Central.&lt;/p&gt;

&lt;h3&gt;
  
  
  JobRunr
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/jobrunr/jobrunr"&gt;JobRunr&lt;/a&gt; is a modern, actively maintained, open source Java library designed for background job processing. JobRunr offers an &lt;strong&gt;easy-to-use API that simplifies job scheduling.&lt;/strong&gt; Unlike Quartz, JobRunr was designed with cloud-native applications in mind and provides support for distributed job scheduling.&lt;/p&gt;

&lt;p&gt;JobRunr aims to be &lt;strong&gt;developer friendly&lt;/strong&gt; by providing a simple, flexible and straightforward API, automatic retries on failure, and seamless integration with your existing infrastructure. In fact, all you need to create a background job is a Java 8 lambda. JobRunr also supports all major SQL databases and popular NoSQL databases.&lt;/p&gt;

&lt;p&gt;A feature of JobRunr that neither Quartz nor db-scheduler provide out of the box is a dashboard. Thanks to the built-in dashboard, it’s &lt;strong&gt;easy to monitor the system&lt;/strong&gt; to see why a job failed or to perform actions such as requeuing or deleting a job.&lt;/p&gt;

&lt;p&gt;Used and trusted by industry leaders, the open source version of &lt;strong&gt;JobRunr provides the essential features you need to schedule tasks in Java&lt;/strong&gt;. You can expect features similar to those in the Quartz Scheduler, but much easier to use. The JobRunr team also develop and maintain JobRunr Pro which offers Enterprise grade features, making JobRunr Pro the best job scheduling library in Java!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.jobrunr.io/en/documentation/5-minute-intro/"&gt;Get started with JobRunr&lt;/a&gt; or &lt;a href="https://www.youtube.com/watch?v=2KFeeFuM9og"&gt;watch Ronald present the motivation, design and code of JobRunr at Spring I/O 2022!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://springbuilders.dev/images/dYbt0s7Gydk0WwDb6wBJ_Q_fgqaq29Yfcqr0O-8ytag/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9zcHJp/bmdidWlsZGVycy5k/ZXYvdXBsb2Fkcy9h/cnRpY2xlcy9vYWJ0/NmIzbjhsNzZwYWtl/dng0MC5wbmc" class="article-body-image-wrapper"&gt;&lt;img src="https://springbuilders.dev/images/dYbt0s7Gydk0WwDb6wBJ_Q_fgqaq29Yfcqr0O-8ytag/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9zcHJp/bmdidWlsZGVycy5k/ZXYvdXBsb2Fkcy9h/cnRpY2xlcy9vYWJ0/NmIzbjhsNzZwYWtl/dng0MC5wbmc" alt="An out of the box monitoring Dashboard provided by JobRunr" width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  DB Scheduler
&lt;/h3&gt;

&lt;p&gt;Another good alternative to Quartz is &lt;a href="https://github.com/kagkarlsson/db-scheduler"&gt;db-scheduler&lt;/a&gt;. The library was originally designed to be &lt;strong&gt;a simpler alternative to Quartz&lt;/strong&gt;. This design reduces the number of required tables to one (compared to Quartz’s 11 tables!) and provides a much simpler API.&lt;/p&gt;

&lt;p&gt;Similar to JobRunr, db-scheduler is &lt;strong&gt;distributed by default&lt;/strong&gt; and guarantees that the same job will not be run concurrently. This guarantee is achieved by using the same centralized DB; in the case of db-scheduler, a relational database is typically required.&lt;/p&gt;

&lt;p&gt;DB Scheduler &lt;strong&gt;provides all the essential features for background job scheduling&lt;/strong&gt;, it can handle fire-and-forget jobs as well as scheduled (a.k.a. delayed) and recurring jobs. The library provides some simple monitoring via Micrometer. It can also perform an automatic retry if a task fails.&lt;/p&gt;

&lt;p&gt;The library is well maintained and growing in features to match the capabilities of Quartz Scheduler, without the extra complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other job scheduling tools
&lt;/h2&gt;

&lt;p&gt;There are several job scheduling alternatives to Quartz that are aimed at a broader audience; we could probably not list them all, even if we wanted to. These alternatives come as standalone services, often referred to as &lt;strong&gt;workflow engines&lt;/strong&gt;, that communicates with your application.&lt;/p&gt;

&lt;p&gt;Using these tools means you’ll &lt;strong&gt;have to get used to the terminology they use&lt;/strong&gt;. In particular, you’ll have to accept that every task, even the simplest, must be defined as a workflow.&lt;/p&gt;

&lt;p&gt;Here we’ll briefly introduce two such workflow engines that work well with Java applications: Temporal and Kestra.&lt;/p&gt;

&lt;h3&gt;
  
  
  Temporal
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://temporal.io/"&gt;Temporal&lt;/a&gt; is an open source &lt;strong&gt;workflow engine designed to make applications more resilient&lt;/strong&gt;. The engine is written in Go and can be interfaced with your Java application using the provided &lt;a href="https://github.com/temporalio/sdk-java"&gt;Java SDK&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Very similar to pure Java-based solutions like JobRunr or Quartz, Temporal provides the essential features that &lt;strong&gt;help developers to focus on business logic&lt;/strong&gt; instead of worrying about the complex mechanisms behind a resilient and distributed scheduler.&lt;/p&gt;

&lt;p&gt;In addition to providing features such as retries, scalability, delayed and recurring executions, Temporal also provides a user interface that &lt;strong&gt;allows you to monitor workflow execution&lt;/strong&gt; to detect failed tasks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kestra
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://kestra.io/"&gt;Kestra&lt;/a&gt; is an open-source &lt;strong&gt;workflow automation platform that makes job scheduling easy&lt;/strong&gt;. The engine is written in Java but aims at running any task in any programming language. Kestra is a great &lt;strong&gt;low-code alternative to Quartz&lt;/strong&gt;. It provides several hundreds of plugin allowing to extract data from any database, cloud storage, or API, and run scripts in any language.&lt;/p&gt;

&lt;p&gt;Kestra &lt;strong&gt;provides a UI for writing or configuring workflows.&lt;/strong&gt; This UI can also be used to monitor the system and track workflow state changes.&lt;/p&gt;

&lt;p&gt;Kestra also has all the essential features required for job scheduling: reliable distributed system, retries, delayed and recurring executions, etc.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;For a long time, there was no real alternative to Quartz for job scheduling in Java. This is no longer the case, as there are several modern tools available to developers. These tools are simpler to use while providing the essential building blocks to achieve reliable and distributed scheduling.&lt;/p&gt;

&lt;p&gt;You may be looking for a persistent task scheduling library that integrates seamlessly into a Java application. In this case, we highly recommend trying out our solution JobRunr. DB Scheduler is another option that can give you satisfactory results.&lt;/p&gt;

&lt;p&gt;You can also take a look at workflow engines such as Temporal or Kestra. These are especially useful if your tasks are written in different programming languages.&lt;/p&gt;

&lt;p&gt;With these diverse tools at their disposal, Java developers can now select the one that best meets their needs, rather than relying on a single option.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Getting started with JobRunr - use case example (pt. II)</title>
      <dc:creator>Donatavict</dc:creator>
      <pubDate>Tue, 28 May 2024 10:53:08 +0000</pubDate>
      <link>https://springbuilders.dev/donatavict/getting-started-with-jobrunr-use-case-example-pt-ii-4hml</link>
      <guid>https://springbuilders.dev/donatavict/getting-started-with-jobrunr-use-case-example-pt-ii-4hml</guid>
      <description>&lt;p&gt;&lt;strong&gt;Authors: &lt;a class="mentioned-user" href="https://springbuilders.dev/auloin"&gt;@auloin&lt;/a&gt; &lt;a class="mentioned-user" href="https://springbuilders.dev/rdehuyss"&gt;@rdehuyss&lt;/a&gt; .&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://springbuilders.dev/donatavict/getting-started-with-jobrunr-introduction-pt-i-2df1"&gt;Read the Part I&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  An example: Automated order fulfillment system using JobRunr
&lt;/h2&gt;

&lt;p&gt;To illustrate how JobRunr works, let’s simulate an &lt;a href="https://en.wikipedia.org/wiki/Order_fulfillment"&gt;order fulfillment&lt;/a&gt; system. Order fulfillment involves tasks required to completely satisfy a customer's order. It's a process where each step can affect another, which happens on several levels. For example, a confirmed order reduces stock, and to avoid stock-outs it is important to monitor and replenish the inventory.&lt;/p&gt;

&lt;p&gt;The order fulfillment workflow has a lot of potential for automation where events can trigger jobs, which in turn trigger events until all steps are completed. As some of the workflow happens in the background, it’s a good opportunity to illustrate JobRunr! The setup of this example is as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;We’ll create an order confirmation that will trigger three tasks that can run asynchronously: 1) send a confirmation email to the customer, 2) notify the warehouse of the arrival of the order, and 3) initiate shipment by calling the preferred carrier’s API.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Some tasks are recurring. We illustrate this using two tasks: 1) a job that generates sales reports monthly, and 2) a job that monitors the inventory on a daily basis.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Something may go wrong while executing the tasks, in those cases, we want to trigger an alert.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This setup will allow to learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;How to setup JobRunr in &lt;a href="https://spring.io/"&gt;Spring Boot&lt;/a&gt; application&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How to enqueue jobs using a Java 8 lambda configured via the &lt;code&gt;@Job&lt;/code&gt; annotation and the &lt;code&gt;JobBuilder&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How to create recurring jobs using the &lt;code&gt;@Recurring&lt;/code&gt; annotation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How to hook into a job lifecycle using a &lt;code&gt;JobFilter&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Please note that this example focuses on JobRunr side of things, we leave the implementation of object such as the Inventory, the &lt;code&gt;Order&lt;/code&gt;, the &lt;code&gt;Product&lt;/code&gt;, etc. as an exercise. Also the running of tasks is simulated by &lt;code&gt;Thread.sleep&lt;/code&gt; which implies explicit handling of &lt;code&gt;InterruptedException&lt;/code&gt;, you probably won't need those in an actual application. Although if you encounter such a case in your project it’s a &lt;strong&gt;best practice to throw the InterruptedException&lt;/strong&gt;, JobRunr will handle it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Setting up JobRunr in a Spring Boot application
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Adding the JobRunr Spring Boot starter dependency
&lt;/h3&gt;

&lt;p&gt;The setup is very easy for Spring Boot, it often consists of adding JobRunr’s Spring Boot starter artifact as a dependency. The following snippet shows how to include it when using Maven as a build tool.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;dependency&amp;gt; 
    &amp;lt;groupId&amp;gt;org.jobrunr&amp;lt;/groupId&amp;gt; 
    &amp;lt;artifactId&amp;gt;jobrunr-spring-boot-3-starter&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;${jobrunr.version}&amp;lt;/version&amp;gt; 
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;JobRunr requires adding database dependencies. We can skip this step as this is usually done when initializing the Spring Boot application. Nonetheless, it’s worth noting that JobRunr &lt;a href="https://www.jobrunr.io/en/documentation/installation/storage/"&gt;supports several databases&lt;/a&gt;. Another DB related feature is that all JobRunr related migrations are automatically handled by default. You can always decide otherwise and &lt;a href="https://www.jobrunr.io/en/documentation/installation/storage/#setting-up-the-database-yourself"&gt;take control over the DB setup&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;JobRunr also needs a JSON processing library, but as Spring Boot by default comes with Jackson support this is already covered.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: JobRunr also supports the older &lt;strong&gt;Spring Boot 2&lt;/strong&gt; and other frameworks such as &lt;strong&gt;Quarkus&lt;/strong&gt; and &lt;strong&gt;Micronaut&lt;/strong&gt;. The library can be &lt;strong&gt;found on Maven Central&lt;/strong&gt;, feel free to use your &lt;strong&gt;preferred build tool&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Configuring JobRunr
&lt;/h3&gt;

&lt;p&gt;JobRunr can be configured using &lt;code&gt;application.properties&lt;/code&gt;. Job processing and the dashboard are disabled by default. To enable them, we’ll add the following to the configuration file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;org.jobrunr.dashboard.enabled=true
org.jobrunr.background-job-server.enabled=true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While we’re still in &lt;code&gt;application.properties&lt;/code&gt; we will also add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;monthly-sales-report.cron=0 0 1 * *
daily-resupply.cron=0 0 * * *

stock-locations=Brussels,Antwerp,Bruges,Liege
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those will be useful to our recurring jobs as we’ll see later!&lt;/p&gt;

&lt;p&gt;JobRunr offers &lt;a href="https://www.jobrunr.io/en/documentation/configuration/spring/#advanced-configuration"&gt;advanced configuration&lt;/a&gt; options allowing to fine tune the application. When using the JobRunr’s Spring Boot starters or the other frameworks integrations, it’s also possible to override the predefined Beans.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing the order fulfillment system
&lt;/h2&gt;

&lt;p&gt;Now that the set up is done, we can start implementing our simulated order fulfillment process. We’ll focus on the JobRunr sides of things, i.e., enqueueing jobs, creating recurring jobs and hooking into a job lifecycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Triggering tasks on order confirmation
&lt;/h2&gt;

&lt;p&gt;Once an order is confirmed, our system will trigger three tasks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Send the order confirmation to the customer.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Notify the warehouse of the arrival of the order so work on the packaging can be swiftly done. This step may also involve generating and optimizing packing slips.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Initiate shipment by making a call to the selected carrier's booking API.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To automate these tasks with JobRunr we will create the following classes: a Spring’s &lt;code&gt;RestController&lt;/code&gt; (namely &lt;code&gt;OrderFulfillmentController&lt;/code&gt;), an &lt;code&gt;OrderFulfillmentService&lt;/code&gt;, and an &lt;code&gt;OrderFulfillmentTasks&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;OrderFulfillmentController&lt;/code&gt; will provide an endpoint to enqueue jobs. These jobs’ logic are implemented by the &lt;code&gt;OrderFulfillmentService&lt;/code&gt;. We could stop here, but we would like the business logic to be separated from the job processing logic which will include features from JobRunr. Thus the introduction of &lt;code&gt;OrderFulfillmentTasks&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here is initial implementation of the &lt;code&gt;OrderFulfillmentService&lt;/code&gt; class, we'll add more to it later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Service
public class OrderFulfillmentService {
    private static final Logger LOGGER = LoggerFactory.getLogger(OrderFulfillmentService.class);
    public void sendOrderConfirmation(UUID orderId) throws InterruptedException {
        // TODO retrieve order from database, generate an invoice and send the confirmation to the customer
        LOGGER.info("Order {}: sending order confirmation%n", orderId);
        Thread.sleep(2000);
    }

    public void notifyWarehouse(UUID orderId) throws InterruptedException {
        // TODO notify the warehouse of the arrival of a new order after retrieving/computing the necessary data using the orderId
        LOGGER.info("Order {}: notifying the warehouse", orderId);
        Thread.sleep(1000);
    }

    public void initiateShipment(UUID orderId) throws InterruptedException {
        // TODO call the carrier's shipment initiation endpoint after retrieving/computing the necessary data using the orderId
        LOGGER.info("Order {}: initiating shipment", orderId);
        Thread.sleep(5000);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note that we use &lt;code&gt;Thread.sleep&lt;/code&gt; to simulate work, this forces us to explicitly handle &lt;code&gt;InterruptedException&lt;/code&gt;, in an actual application, it’s probably not needed.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;OrderFulfillmentTasks&lt;/code&gt; class makes use of the &lt;code&gt;OrderFulfillmentService&lt;/code&gt;. Notice the additional &lt;code&gt;@Job&lt;/code&gt;, which allows setting values to a job’s attributes. This annotation is very handy! You may also use the alternative: the &lt;code&gt;JobBuilder&lt;/code&gt;. We're doing exactly that in the body of the method, the jobs are configured using the &lt;code&gt;JobBuilder&lt;/code&gt; and then saved atomically. This method is essentially a job that creates other jobs, so it also benefits from JobRunr's fault tolerance capabilities.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Service
public class OrderFulfillmentTasks {

    private final OrderFulfillmentService orderFulfillmentService;

    public OrderFulfillmentTasks(OrderFulfillmentService orderFulfillmentService) {
        this.orderFulfillmentService = orderFulfillmentService;
    }

    @Job(name = "order-%0")
    public void enqueueConfirmedOrderTasks(UUID orderId) {
        BackgroundJob.create(of(
                aJob()
                        .withName(format("order-%s-confirmation", orderId))
                        .withDetails(() -&amp;gt; orderFulfillmentService.sendOrderConfirmation(orderId)),
                aJob()
                        .withName(format("order-%s-warehouse-notification", orderId))
                        .withAmountOfRetries(20)
                        .withDetails(() -&amp;gt; orderFulfillmentService.notifyWarehouse(orderId)),
                aJob()
                        .withName(format("order-%s-shipment-initiation", orderId))
                        .withDetails(() -&amp;gt; orderFulfillmentService.initiateShipment(orderId))
        ));
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the use of &lt;code&gt;withAmountOfRetries(20)&lt;/code&gt; for the warehouse notification task, we have to increase the amount of retries since our internal service is quite unstable.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;OrderFulfillmentController&lt;/code&gt; is quite simple as it exposes a single endpoint that requests JobRunr to enqueue jobs. Once the metadata of these jobs are saved in the database, the server workers will process them asynchronously when they are ready.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@RestController
public class OrderFulfillmentController {

    @GetMapping("/confirm-order")
    public String confirmOrder() {
        UUID orderId = UUID.randomUUID();
        BackgroundJob.&amp;lt;OrderFulfillmentTasks&amp;gt;enqueue(x -&amp;gt; x.enqueueConfirmedOrderTasks(orderId));

        return "Done";
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Creating reccuring tasks
&lt;/h2&gt;

&lt;p&gt;The first part of our order fulfillment system is done. Now, we’ll add recurring tasks to know how well the company is doing and to make sure we never go out-of-stock!&lt;/p&gt;

&lt;p&gt;Let’s update the &lt;code&gt;OrderFulfillmentService&lt;/code&gt; to add the logic for these two tasks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Service
public class OrderFulfillmentService {
    // ...

    // Add the following methods to the class

    public void generateMonthlySalesReport(YearMonth month) throws InterruptedException {
        // TODO aggregate monthly sales, generate PDF and send it to managers
        LOGGER.info("Sales report: generating monthly sales report on {} for month {}", Instant.now(), month);
        Thread.sleep(10000);
    }

    public void resupply(String stockLocation) throws InterruptedException {
        // TODO check the stock and contact supplier if needed
        LOGGER.info("Inventory resupply: resupplying the inventory on {} for stock {}", Instant.now(), stockLocation);
        Thread.sleep(5000);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now update the &lt;code&gt;OrderFulfillmentTasks&lt;/code&gt; to register those tasks. As they are recurring, we annotate them with &lt;code&gt;@Recurring&lt;/code&gt; and a few attributes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Service
public class OrderFulfillmentTasks {

   // Add the following attribute to the class

    @Value("${stock-locations}")
    private List&amp;lt;String&amp;gt; stockLocations;

    // ...

    // Add the following methods to the class

    @Recurring(id = "monthly-sales-report", cron = "${monthly-sales-report.cron}", zoneId = "Europe/Brussels")
    public void generateMonthlySalesReport() throws InterruptedException {
        YearMonth previousMonth = YearMonth.now().minusMonths(1);
        orderFulfillmentService.generateMonthlySalesReport(previousMonth);
    }

    @Recurring(id = "daily-resupply", cron = "${daily-resupply.cron}", zoneId = "Europe/Brussels")
    public void resupply(JobContext jobContext) throws InterruptedException {
        JobDashboardProgressBar jobDashboardProgressBar = jobContext.progressBar(stockLocations.size());
        for(String stockLocation : stockLocations) {
            orderFulfillmentService.resupply(stockLocation);
            jobDashboardProgressBar.increaseByOne();
            jobContext.logger().info(format("Resupplied stock %s", stockLocation));
        }
    }

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;JobRunr will automatically register methods annotated with &lt;code&gt;@Reccuring&lt;/code&gt; and schedule them for execution at the specified times!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note the use of application properties, we promised to come back to them. We use them to configure the CRON expressions of our recurring jobs. The last property is used to provide the locations of our different warehouses. Here we have assumed that our imaginary company operates from Belgium and set the zoneId accordingly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Triggering an alert on task failure
&lt;/h2&gt;

&lt;p&gt;The system may encounter an issue that causes jobs to fail during execution. In our scenario, it’s important to alert the operations team so they can make sure the failed task is one way or another completed.&lt;/p&gt;

&lt;p&gt;JobRunr allows us to hook into a job lifecycle using &lt;a href="https://www.jobrunr.io/en/documentation/pro/job-filters/"&gt;Job Filters&lt;/a&gt;. The following code simulates the idea of sending a notification when all retries have been exhausted.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Component
public class OrderFulfilmentTasksFilter implements JobServerFilter {
    private static final Logger LOGGER = LoggerFactory.getLogger(OrderFulfilmentTasksFilter.class);

    @Override
    public void onFailedAfterRetries(Job job) {
        // TODO alert operational team of Job failure
        LOGGER.info("All retries failed for Job {}", job.getJobName());
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;JobRunr does not automatically register custom job filters. We need additional code to make sure our hook will be called by JobRunr’s background job servers. We can achieve this by overriding the &lt;code&gt;BackgroundJobServer&lt;/code&gt; bean. Here, we illustrate another approach using Spring’s &lt;code&gt;BeanPostProcessor&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Component
public class BackgroundJobServerBeanPostProcessor implements BeanPostProcessor {
    private final OrderFulfilmentTasksFilter orderFulfilmentTasksFilter;

    public BackgroundJobServerBeanPostProcessor(OrderFulfilmentTasksFilter orderFulfilmentTasksFilter) {
        this.orderFulfilmentTasksFilter = orderFulfilmentTasksFilter;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if(bean instanceof BackgroundJobServer backgroundJobServer) {
            backgroundJobServer.setJobFilters(Collections.singletonList(orderFulfilmentTasksFilter));
        }
        return bean;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Running the application
&lt;/h2&gt;

&lt;p&gt;And we’re done, let’s enjoy the results! Start the application and head over to &lt;a href="http://localhost:8000/dashboard"&gt;http://localhost:8000/dashboard&lt;/a&gt;. You should land on a beautiful dashboard, feel free to visit other pages.&lt;br&gt;
Once you’re satisfied, visit &lt;a href="http://localhost:8080/confirm-order"&gt;http://localhost:8080/confirm-order&lt;/a&gt; to trigger the order fulfillment tasks. Head back to the dashboard, you can observe the jobs moving from one state to another until they succeed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://springbuilders.dev/images/RVM0sv71WechAh4pLCsdhgK_vD48-Xb-kwZCfutUkz8/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9zcHJp/bmdidWlsZGVycy5k/ZXYvdXBsb2Fkcy9h/cnRpY2xlcy84ZXFl/NjdwcTFuOTZmdW5j/MHVzYy5wbmc" class="article-body-image-wrapper"&gt;&lt;img src="https://springbuilders.dev/images/RVM0sv71WechAh4pLCsdhgK_vD48-Xb-kwZCfutUkz8/rt:fit/w:800/g:sm/q:0/mb:500000/ar:1/aHR0cHM6Ly9zcHJp/bmdidWlsZGVycy5k/ZXYvdXBsb2Fkcy9h/cnRpY2xlcy84ZXFl/NjdwcTFuOTZmdW5j/MHVzYy5wbmc" alt="Monitor jobs processing by visiting the jobs page of JobRunr dashboard" width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you haven’t done so yet, visit &lt;a href="http://localhost:8000/reccuring-jobs"&gt;http://localhost:8000/reccuring-jobs&lt;/a&gt; to see the recurring jobs. Have fun triggering them!&lt;/p&gt;

&lt;h2&gt;
  
  
  Going further
&lt;/h2&gt;

&lt;p&gt;We could not include all the features of JobRunr in our example. We encourage interested developers to explore the library. You can &lt;a href="https://github.com/jobrunr/example-order-fulfillment"&gt;fork the example&lt;/a&gt; project and make it your own! We also have a few more examples to get you started: &lt;a href="https://github.com/jobrunr/example-spring"&gt;example-spring&lt;/a&gt;, &lt;a href="https://github.com/jobrunr/example-quarkus"&gt;example-quarkus&lt;/a&gt;, &lt;a href="https://github.com/jobrunr/example-micronaut"&gt;example-micronaut&lt;/a&gt;. If you're having trouble, you can &lt;a href="https://www.jobrunr.io/en/documentation/"&gt;find most of the information you need in the documentation&lt;/a&gt; or &lt;a href="https://github.com/jobrunr/jobrunr/discussions"&gt;start a discussion on Github&lt;/a&gt; to get help.&lt;/p&gt;

&lt;p&gt;Here is a non-exhaustive list of potential things to explore:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Distributed processing:&lt;/strong&gt; if our imaginary company gets successful a single machine will probably not be enough to handle all the tasks. See what happens when you run two or more of the example applications in parallel. Note: it’ll not work with an in memory database.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scheduling jobs:&lt;/strong&gt; technically recurring jobs are scheduled (unless they are triggered manually). You could also create non recurring &lt;a href="https://www.jobrunr.io/en/documentation/background-methods/scheduling-jobs/"&gt;scheduled tasks&lt;/a&gt; (a.k.a., delayed jobs).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Retries:&lt;/strong&gt; It may be necessary to &lt;a href="https://www.jobrunr.io/en/documentation/background-methods/dealing-with-exceptions/"&gt;change the amount of retries&lt;/a&gt; as some jobs should not be retried at all. In the example we do so using the &lt;code&gt;JobBuilder&lt;/code&gt;, the same can be achieved via the &lt;code&gt;@Job&lt;/code&gt; annotation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dashboard:&lt;/strong&gt; delete or requeue jobs, check servers statuses on the servers page, notifications on the dashboard page (if any)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Best practices&lt;/strong&gt;: read on the &lt;a href="https://www.jobrunr.io/en/documentation/background-methods/best-practices/"&gt;best practices to follow&lt;/a&gt; when using JobRunr&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Logging job progress:&lt;/strong&gt; keep an eye on the &lt;a href="https://www.jobrunr.io/en/documentation/background-methods/logging-progress/"&gt;progress of a job&lt;/a&gt; by adding a progress bar. If you want to see this live in action, trigger the recurring job that with id &lt;code&gt;daily-resupply&lt;/code&gt;, find the job on the jobs page, click on the job id and wait until it starts processing 👀&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Different ways of configuring jobs:&lt;/strong&gt; &lt;a href="https://www.jobrunr.io/en/documentation/background-methods/enqueueing-jobs/"&gt;check out ‘Enqueueing jobs‘&lt;/a&gt; for some usage examples on how to enqueue jobs configured using annotations or builders.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;JobRunr is a great solution for processing your background jobs. It can be run in a distributed system and has built-in monitoring. The library is actively maintained, which means it's always getting new features and improving existing ones. That’s in part thanks to the great community helping us by contributing and providing feedback. We'd love for you to contribute or provide feedback on &lt;a href="https://github.com/jobrunr/jobrunr"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To make open source development sustainable, we develop and maintain JobRunr Pro. It offers &lt;a href="https://www.jobrunr.io/en/documentation/pro/"&gt;Enterprise grade features&lt;/a&gt; such as Batches, Rate Limiters, Dashboard Single Sign-On (SSO), and many more. If you need those extra features, consider taking a subscription. You’ll not only support open source development but also our &lt;a href="https://www.jobrunr.io/en/blog/2024-01-18-trees-planted/"&gt;planet&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;The complete source code for the example is available &lt;a href="https://github.com/jobrunr/example-order-fulfillment"&gt;over on GitHub&lt;/a&gt;.&lt;br&gt;
Check out the video of &lt;a href="https://www.youtube.com/watch?v=e9POHS0BjEg&amp;amp;t=1s&amp;amp;ab_channel=SpringDeveloper"&gt;Josh Long's review&lt;/a&gt; of JobRunr.&lt;/p&gt;

</description>
      <category>java</category>
      <category>backgroundjobs</category>
      <category>taskscheduler</category>
    </item>
    <item>
      <title>Getting started with JobRunr - Introduction (pt. I)</title>
      <dc:creator>Donatavict</dc:creator>
      <pubDate>Fri, 17 May 2024 12:31:19 +0000</pubDate>
      <link>https://springbuilders.dev/donatavict/getting-started-with-jobrunr-introduction-pt-i-2df1</link>
      <guid>https://springbuilders.dev/donatavict/getting-started-with-jobrunr-introduction-pt-i-2df1</guid>
      <description>&lt;h2&gt;
  
  
  Task scheduler in Java
&lt;/h2&gt;

&lt;p&gt;JobRunr is an open-source Java library for task scheduling and distributed background job processing. It offers an effortless way to perform background tasks using only Java 8 lambdas.&lt;/p&gt;

&lt;p&gt;Whether it is a fire-and-forget, scheduled or recurring job, JobRunr analyzes the lambda and stores the metadata needed to process the job in a database. This simple architecture, with a few other mechanisms, allows a job to be executed on any available server running your application (👋 k8s).&lt;/p&gt;

&lt;p&gt;In the event of a failure, JobRunr automatically retries the job. Thanks to the built-in dashboard, it's easy to monitor the system to see why a job failed or to perform actions such as requeuing or deleting a job.&lt;/p&gt;

&lt;p&gt;The software integrates easily into any Java application by adding JobRunr as a dependency. It also works seamlessly with frameworks like Spring Boot, Quarkus, and Micronaut.&lt;/p&gt;

&lt;h2&gt;
  
  
  JobRunr use cases in Java
&lt;/h2&gt;

&lt;p&gt;The applications of JobRunr are almost limitless: anything that can be done in Java can be automated and scheduled with JobRunr. Would you like to send mass notifications, fire webhooks, process images, or videos? JobRunr can do it all for you, so you can focus on solving the business problems at hand.&lt;/p&gt;

&lt;p&gt;JobRunr is used in various industries from retail to healthcare to marketing, no matter the size of your company. We have written a few articles about how JobRunr Pro is &lt;a href="https://www.jobrunr.io/en/use-case/"&gt;used&lt;/a&gt; in those industries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core task scheduler features for Developers:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Developer friendly:&lt;/strong&gt; the API of JobRunr is simple, flexible and straightforward - with a simple, yet extensive set up.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Simple adoption:&lt;/strong&gt; JobRunr fits into any software architecture and requires little changes to your codebase while requiring a low amount of dependencies.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Framework integration:&lt;/strong&gt; JobRunr supports the most popular Java frameworks: Spring, Quarkus and Micronaut.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Various storage options:&lt;/strong&gt; persistent storage is done via either RDBMS (e.g. Postgres, MariaDB/MySQL, Oracle, SQL Server, DB2, and SQLite) or NoSQL (ElasticSearch, MongoDB and Redis)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cloud-native and cloud agnostic:&lt;/strong&gt; deployable anywhere, either on your preferred cloud provider or on-premise&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Distributed processing:&lt;/strong&gt; scale is not an issue - JobRunr supports job distribution across a cluster.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fault tolerance:&lt;/strong&gt; JobRunr is also fault-tolerant - is an external web service down? No worries, the job is automatically retried 10-times with a smart back-off policy. The retry policy is of course configurable!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Real-time monitoring:&lt;/strong&gt; JobRunr provides a dashboard that allows users to monitor jobs, trigger or delete recurring jobs, and requeue or delete jobs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Virtual threads:&lt;/strong&gt; JobRunr supports virtual threads to allow for increased throughput of I/O jobs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Comprehensive documentation:&lt;/strong&gt; the documentation covers everything from setup to advanced features, easing the learning curve.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stay tuned for more of getting started with JobRunr and its implementation!😉&lt;/p&gt;

&lt;p&gt;Authors: Ismaila Abdoulahi, Ronald Dehuysser, Donata Petkeviciute&lt;/p&gt;

</description>
      <category>java</category>
      <category>backgroundjobs</category>
      <category>taskscheduler</category>
    </item>
  </channel>
</rss>
