Out of nothing other than sheer curiosity, I was wondering how Kotlin (and by extension, Java) handles scheduling tasks. Let’s say that we have a function that we wish to run every 5 seconds. How can we go about achieving that in Kotlin?
That was the question I set out to answer. In this post I will share with you my (admittedly basic) observations and learnings.
Here is a brief introduction to the four libraries we’ll be covering in this beginner friendly tour of task scheduling with Kotlin:
- Timer: Timer is a comparatively simple scheduling library included in the Kotlin standard library. It allows you to schedule tasks to run at a fixed rate or delay. It seems like an obvious choice for simple scheduling needs and appears nice and easy to use.
- Quartz: Quartz is a much more powerful scheduling library that provides features like multi-threaded execution, recovery from missed tasks, and support for complex scheduling requirements using cron expressions. It appears to be the best choice for more complex scheduling needs.
- Cron4j: Cron4j is another powerful scheduling library that provides similar features to Quartz, but with a simpler API and appears a little more unloved. It too supports cron expressions for complex scheduling requirements.
- Spring Scheduler: Spring Scheduler is a scheduling library that is part of the Spring Framework. It provides a simple and flexible way to schedule tasks using annotations. It is probably the best choice if you are already using Spring in your project.
What Task Will We Be Scheduling?
To give each library a fair chance, we will try to do the same thing using all four task scheduling choices.
The aim is to be able to run a simple println
function every 5 seconds that prints out the current time.
I know this is a very basic test. This is more about getting some hands on with each library, and actually using Kotlin, rather than trying to do too much, too soon.
Hopefully the code to actually achieve the visual outcome will be as simple as:
println("This task ran at: ${Date.from(Instant.now())}")
Code language: Kotlin (kotlin)
With the scene set, let’s get on with the show.
Kotlin’s StdLib Timer
By far the simplest way to schedule a task using Kotlin is to use the Timer
class.
I believe Timer
is a standard Java class, and the scheduler functionality is added using Kotlin extension functions. That’s certainly how the documented code seems to explain it.
The code is really straightforward:
Timer()
.schedule(timerTask {
println("Hello from StdLib Timer: ${Date.from(Instant.now())}")
}, 0L, 5000L)
Code language: Kotlin (kotlin)
No variables needed here, but we could modify this code to store an instance of the Timer
class and then do further things with it. An example of that follows below.
Timer
is the class from the Kotlin Standard Library (Std Lib). We call Timer()
to create a new instance of the class, and then immediately use that instance by calling the schedule
method.
schedule
has a variety of ways in which it can be called, but all start by taking an instance of TimerTask
as the first argument.
Probably the most confusing part of this code, for me at least, was exactly how the call of timerTask { ... }
ends up creating a new instance of a TimerTask
. That syntax was new to me. What’s actually happening there deserves its own post, so we won’t dive into it here.
If we continue on, timerTask
creates a new instance of a TimerTask
and the code inside the lambda (the {
and }
) is what will actually be executed on each tick of the timer. In this case it’s the standard println
output we covered above.
The two other arguments: 0L
and 5000L
are two time values expressed as Longs. I’m taking a guess here that the reason to use Longs rather than Ints is down to the use of milliseconds. Ints have a much smaller maximum range than longs, some like 2 billion for Ints versus 9 Quintilian for Longs.
To put that into perspective, a billion has three commas, a Quintilian has six.
0L
is the initial delay – in this case, run immediately.
5000L
is the interval.
Both are expressed in milliseconds.
So immediately when the code is executed, and every 5 seconds thereafter, the println
function should be called.
Hello from StdLib Timer: Mon May 08 11:42:48 BST 2023
Hello from StdLib Timer: Mon May 08 11:42:53 BST 2023
Hello from StdLib Timer: Mon May 08 11:42:58 BST 2023
Code language: Shell Session (shell)
Using A Timer Variable
I said above that you could expand the code further to store the Timer
instance into a variable, and then ‘do stuff’ with it.
The basic example I came up with was as follows:
val timer = Timer()
timer.schedule(timerTask {
println("Hello from StdLib Timer: ${Date.from(Instant.now())}")
}, 0L, 5000L)
Thread.sleep(25000L)
timer.cancel()
Code language: Kotlin (kotlin)
The println
remains the same, but by throwing in a Thread.sleep
call, we can then wait around for 25 seconds and terminate the execution cleanly:
Hello from StdLib Timer: Mon May 08 11:44:09 BST 2023
Hello from StdLib Timer: Mon May 08 11:44:14 BST 2023
Hello from StdLib Timer: Mon May 08 11:44:19 BST 2023
Hello from StdLib Timer: Mon May 08 11:44:24 BST 2023
Hello from StdLib Timer: Mon May 08 11:44:29 BST 2023
Process finished with exit code 0
Code language: CSS (css)
There’s likely more you can do here, but that was enough for me to prove the concept.
Timer Pros
Timer seems like it is a good choice for fairly simple tasks that need to be executed periodically with a fixed delay between calls.
Perhaps if you need to periodically download a file from a remote server and save it locally, Timer might be sufficient.
Timer Cons
From what I’ve read, Timer starts to lack when things get a little more serious.
Timer runs in a single thread, so if a task takes a long time to execute, it can delay the execution of subsequent tasks. This can lead to scheduling irregularities.
Timer cannot recover from missed tasks. If a task is scheduled to run every 5 seconds, but the previous execution took 10 seconds, the next execution will be delayed by 5 seconds. This can cause the execution times to drift over time, which may not be acceptable in some applications.
Timer does not provide a way to express complex scheduling requirements, such as “run this task every weekday at 8:00 AM”.
Kotlin Task Scheduling With Quartz
Quartz was the second task scheduling library I tried.
That’s why it’s going here as the second library.
However, I genuinely think it is the best library out of all four that I tried.
The code isn’t that much more complex than using the Timer
version above, but the flexibility is substantially greater, even for a newbie like myself.
Setup
To use Quartz you will need to add the dependency to your project.
This is as simple as updating the build.gradle.kts
file as follows:
dependencies {
implementation("org.quartz-scheduler:quartz:2.3.2")
testImplementation(kotlin("test"))
}
Code language: JavaScript (javascript)
I’m using version 2.3.2
because that’s the latest stable version. How I found that out was by using Maven Repository:
If you then click on the specific version number – 2.3.2
in this case – you get taken to a page where you can then select your build system. I am using Gradle with Kotlin, so I got the following:
Which is pretty nice. Just copy / paste that into your build.gradle.kts
file and IntelliJ should pop up a little icon to let your synchronise your new project dependencies:
That should be all you need to do.
Implementation
Quartz is a Java library, and so working with it from Kotlin wasn’t quite as easy as following the good number of tutorials over on the Quartz GitHub repo. Sadly.
Mostly I used tutorial-lesson-01.md
as my point of reference. I’ve not linked directly to that as it’s a GitHub blob address and they have a bad tendency to break.
However, it wasn’t that hard for me to figure out, either. And remember, I am both a Kotlin AND Java newbie. So if I can do it, you bet you can too.
Here’s the code I came up with:
package org.example.schedulers
import org.example.jobs.QuartzJob
import org.quartz.JobBuilder
import org.quartz.SimpleScheduleBuilder
import org.quartz.TriggerBuilder
import org.quartz.impl.StdSchedulerFactory
fun quartzScheduler() {
val schedulerFactory = StdSchedulerFactory()
val scheduler = schedulerFactory.scheduler
scheduler.start()
val job = JobBuilder.newJob(QuartzJob::class.java).build()
val trigger = TriggerBuilder.newTrigger()
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5))
.build()
scheduler.scheduleJob(job, trigger)
}
Code language: Kotlin (kotlin)
That’s quite a lot of code, and if you gave it more than a cursory glance (hey, I skim read tech tutorials too, don’t feel bad!) then you may have been like, Chris, where the heck is the println
?
Good point.
I split it out into another class.
package org.example.jobs
import org.quartz.Job
import org.quartz.JobExecutionContext
import java.time.Instant
import java.util.Date
class QuartzJob : Job {
override fun execute(context: JobExecutionContext?) {
println("Hello from Quartz Job: ${Date.from(Instant.now())}")
}
}
Code language: Kotlin (kotlin)
This could be inlined. I will cover how to do that below. The reason I didn’t inline it is that in a real project, there is almost no way I would ever have the complexity necessary to use Quartz, whilst simultaneously having the simplicity to inline the called code. I hope that makes sense.
Code Walkthrough
Let’s cover the code now, line by line.
val schedulerFactory = StdSchedulerFactory()
Fairly straightforward. We create a new instance of the StdSchedulerFactory
class. I copied this straight from the tutorial docs, but imported the dependency path (line 7).
Next comes a little Kotlin shorthand magic:
The code I started with was:
val scheduler = schedulerFactory.getScheduler()
Looks fairly standard, right?
I need a new instance of the Scheduler
which the schedulerFactory
can provide. So I call that method. But IntelliJ pops up a little hint / suggested refactoring to drop the getter method, and instead replace it with property access syntax.
I’d actually argue that this is very unintuitive for non-Kotlin natives. However, I guess if you are fluent with Kotlin, it is standard stuff.
Whilst not obvious at all from the code sample above – because the Syntax Highlighting plugin just isn’t that good – things do look slightly more interesting in IntelliJ:
So yeah, a nice shade of pink, or purple. I’m not sure which. But it looks somehow different.
OK, onwards.
scheduler.start()
– starts the scheduler instance. Again, straight from the tutorial docs – Once a scheduler is instantiated, it can be started, placed in stand-by mode, and shutdown.
That’s all the mandatory setup stuff done.
Calling Through To Java
On line 14 we have val job = JobBuilder.newJob(QuartzJob::class.java).build()
Classes creating classes. Coming from a more functional language like JavaScript, this might feel a little heavy. But it’s fine. It’s just different.
newJob
takes a class that implements Quartz’s Job
interface. In this case, that would be the separate class I created above, the QuartzJob
class.
That interface defines a single method: execute
, which is the code that will be called every time to job is executed.
The most interesting part of this syntax, as far as I am concerned, is QuartzJob::class.java
.
In Kotlin, when we suffix a class name with .java
suffix we can get a reference to the actual Java class object that corresponds to the Kotlin class.
As best I understand it. the Kotlin class
keyword returns a Kotlin KClass
object, which provides information about that class at runtime.
However, sometimes such as in this example, we need to access the Java Class
object for interoperability with Java code or certain Java-based frameworks or libraries, like the Quartz library in this case.
The .java
suffix is a way to get the Java Class
object from the KClass
object in Kotlin. It is seemingly equivalent to calling the Java getClass()
method on an object.
Building & Starting The Scheduled Job
Lines 16 through 20 cover off creating a trigger. A Trigger
is the way in which Jobs
are scheduled by Quartz.
First we create a new instance of a Trigger
with TriggerBuilder.newTrigger()
.
Then with that Trigger
we define the schedule.
Even if you don’t understand any other part of this code, SimpleScheduleBuilder.repeatSecondlyForever(5)
is surely the most intuitive command we have covered.
The last method in the fluent chain of line’s 16 through 18 calls .build()
. This is the Builder creational design pattern in action, which allows the API consumer (us, in this case) to create what are likely potentially complex objects in a step-by-step manner.
Forgetting to call the .build()
method results in a bad time, because you end up with an instance of the Builder, rather than the object you intended to build. Whoops.
Finally, after all that, we can start the job:
scheduler.scheduleJob(job, trigger)
Code language: Kotlin (kotlin)
As the code suggests, this schedules the job to be executed by the scheduler at the specified trigger interval. In this case, the job will be executed every 5 seconds.
The Outcome
The outcome is that this code then ticks along, every 5 seconds printing out the date and time to the console, until the program is terminated:
Hello from Quartz Job: Mon May 08 19:25:31 BST 2023
Hello from Quartz Job: Mon May 08 19:25:36 BST 2023
Hello from Quartz Job: Mon May 08 19:25:41 BST 2023
Hello from Quartz Job: Mon May 08 19:25:46 BST 2023
Process finished with exit code 130 (interrupted by signal 2: SIGINT)
Code language: CSS (css)
And whilst that is a lot of words to walk through this code, I feel it’s actually fairly easy to work with, when not having to give a fairly thorough explanation of each line’s purpose.
The main thing is that this code does exactly what we wanted. It prints out the date / time in a nicely formatted manner, in a clean and extensible fashion, and with a minimum of fuss.
Can the remaining libraries here compete?
Let’s see.
Quartz Pros
Personally I found Quartz to be the nicest API to work with. It felt like I knew what was happening, and was in complete control at all times.
Here are some other plus points I discovered about Quartz:
- Multi-threaded execution, which ensures that long-running tasks do not block the execution of other tasks.
- Recovery from missed tasks, which ensures that the task is executed at the next scheduled time even if it was missed due to a previous execution taking too long.
- Support for complex scheduling requirements, such as cron expressions that allow you to specify complex schedules like “run this task every weekday at 8:00 AM”.
Quartz Cons
The learning curve is higher than Timer.
It’s another dependency to add to the project.
There’s comparatively a lot more code required to do all the setup steps than in the other examples.
With so many features and capabilities, maybe it’s going to be less performant or memory hungry than the other options? Not sure on this one.
Kotlin Task Scheduling with Cron4j
Cron4J was the third task scheduling library I tried.
I was a little concerned when I took a look at Maven Repository and saw this library hadn’t seen an update since July 2013.
However, with no known vulnerabilities, so long as the code is feature complete, it needn’t have to be regularly updated to do what it set out to achieve.
The code for the Cron4J implementation is short and sweet:
package org.example.schedulers
import it.sauronsoftware.cron4j.Scheduler
import org.example.jobs.Cron4JJob
fun cron4JScheduler() {
val scheduler = Scheduler()
scheduler.schedule("* * * * *", Cron4JJob())
scheduler.start()
}
Code language: Kotlin (kotlin)
And the code for my Cron4JJob
class is as follows:
package org.example.jobs
import java.time.Instant
import java.util.Date
class Cron4JJob : Runnable {
override fun run() {
println("Hello from Cron4J Job: ${Date.from(Instant.now())}")
}
}
So, super easy really.
However, there’s one big problem here.
It doesn’t actually solve the requirement of running every 5 seconds.
In the cron4JScheduler
function, we first create a new instance of the Scheduler
class provided by the cron4j
library and we assign it to a variable called scheduler
.
Then we have the following line:
scheduler.schedule("* * * * *", Cron4JJob())
Code language: Kotlin (kotlin)
And this where the problem lies.
We have to use the cron expression syntax, and as best I am aware that doesn’t have the granularity of seconds.
The "* * * * *"
syntax means “run every minute”.
You can use a website like Crontab.guru to better understand / create cron expressions:
There’s no way to change this to run more frequently than once a minute. With a crontab you can come up with hacks / workarounds to run more frequently, either using shell scripts or multiple calls with sleep
commands, probably other methods, too. But not so here.
I mean, this works well enough. It just doesn’t do what we actually want it to do.
Hello from Cron4J Job: Mon May 08 19:35:00 BST 2023
Hello from Cron4J Job: Mon May 08 19:36:00 BST 2023
Hello from Cron4J Job: Mon May 08 19:37:00 BST 2023
Hello from Cron4J Job: Mon May 08 19:38:00 BST 2023
Process finished with exit code 130 (interrupted by signal 2: SIGINT)
Code language: CSS (css)
And that then only leaves us with one final option to test.
Cron4J Pros
It’s great if you are looking for a library that puts cron expressions front and centre.
Compared to Quartz there was less setup involved from a code writing perspective.
Fewer options for configuration mean fewer things to learn or get confused by.
Cron4J Cons
It’s not under active development.
The project has been archived on GitHub so there’s no way to see previous issues or pull requests. This alone is enough would be enough to make me avoid it.
Kotlin Task Scheduling with Spring Scheduler
The code to print a message every five seconds using Spring Boot is the easiest* of the lot.
And by easiest, I mean there’s clearly a heck of a lot happening behind the scenes to make this work, which we not only do not see, but don’t even need to understand.
That may be perfect for you.
Or it may leave you with an uneasy feeling.
I’m somewhere in the middle. I trust Spring Boot to be very well made, well tested, and well meaning.
But I’d love to know exactly how it works under the hood. However, that is a huge ask. Spring is massive. It’s the sort of thing you could devote a year too and barely scratch the surface.
Anyway, in order to get this next example working, I had to switch to a brand new project. I could have made a Spring Boot project and then imported the other two dependencies from above, but somehow that didn’t feel right.
Instead, I chose to split the Spring Boot / Spring Scheduler example into a standalone project.
There’s probably a way to import and run just the Spring Scheduler code. But I’m not that skilled at this time, so this is the way I could get it to work.
Implementation
As above, I created a brand new project using the Spring Initializr.
There’s nothing special about this.
No extra dependencies needed.
The main thing, I think, is the build.gradle.kts
file, which looks like this:
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("org.springframework.boot") version "3.0.6"
id("io.spring.dependency-management") version "1.1.0"
kotlin("jvm") version "1.7.22"
kotlin("plugin.spring") version "1.7.22"
}
group = "com.example"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_17
repositories {
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter")
implementation("org.jetbrains.kotlin:kotlin-reflect")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "17"
}
}
tasks.withType<Test> {
useJUnitPlatform()
}
Code language: Kotlin (kotlin)
I did not change this in any way from the generated version from Spring Initializr.
Code Walkthrough
Here’s the full code to make this run:
package com.example.testingschedules
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.scheduling.annotation.EnableScheduling
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Component
import java.time.Instant
import java.util.*
@SpringBootApplication
@EnableScheduling
class TestingSchedulesApplication
fun main(args: Array<String>) {
runApplication<TestingSchedulesApplication>(*args)
}
@Component
class MyTask {
@Scheduled(fixedRate = 5000)
fun printMessage() {
println("Hello from Spring Boot Job: ${Date.from(Instant.now())}")
}
}
Code language: Kotlin (kotlin)
I’ve highlighted everything I needed to add to the starter code (which is everything else that is not highlighted) to make this work.
I’ll cover off the non-standard stuff.
Line’s 8 and 9 are required for printing out the date / time on line 23.
@EnableScheduling
is the annotation that is used to enable Spring’s scheduling functionality. I found this on the docs, and whilst this is an annotation, the documentation is extensive – albeit found via code docs.
Lines 19-25 again come from the Spring Scheduler tutorial.
We need the @Component
annotation to mark that class being Spring-managed. Spring will then handle creation of an instance of MyTask
for us without us having to explicitly create a new
instance.
We add yet another annotation on line 21 – @Scheduled(fixedRate = 5000)
.
I think fixedRate
is a slightly less friendly name than we saw in Quartz which had repeatSecondlyForever
, but the outcome is the same. This code will be executed every 5 seconds, forever.
And finally we have the actual function code, which is basically the same as we’ve had in the previous three examples.
So, much easier in many regards, but clearly a lot happening behind the scenes that we are completely unaware of.
Spring Scheduler Pros
Backed by the Spring Framework, this is going to be a popular, widely used, battle tested approach to solving the problem of scheduled tasks in Kotlin or Java.
Even though I had to import a full Spring Boot project, the code to get a scheduled task running – from my point of view – was very simple. I appreciate there’s a lot going on under the hood to provide me with that simplicity.
It’s already part of the Spring Framework. It wasn’t like I had to import the Spring Framework and another Spring dependency. So if you’re using Spring Boot, you already have this.
Spring Scheduler Cons
I had to set up a whole new Spring Boot project just to try this out. It may very well be that, as a newbie, I don’t know how to pull in just the Spring Scheduler. But the docs did not spell this out. Maybe it’s not even possible?
That brings about the second point: the learning curve for Spring Framework is scary intimidating. I’d love to know more about it, and am actively learning, but boy is there a lot to learn. I feel like I brought a steamroller to push in a thumbtack.
Wrapping Up
I’m glad I’ve had a chance to try out Timer, Quartz, Cron4J, and Spring Scheduler. Each approach has given me a better understanding of how to schedule simple and small tasks in a Kotlin project.
Timer is the obvious choice to reach for first. It’s built in, simple to use, and does the job.
However, when things get more serious, I’d be looking at either Quartz or Spring Scheduler.
If I was already using Spring Framework then Spring Scheduler is a no-brainer.
I would use Quartz in the first instance for everything else.
For me, Cron4J is not a library I would reach for. In archive mode on GitHub, and with pull requests and GitHub issues hidden, I have no idea what potential headaches may arise, and no direct hope of talking to the devs if things did go wrong. Aside from that, the library itself seems fine.
Ultimately it’s not hard to try out each library / approach. As ever with tasks like this, it’s probably more scary / off-putting just getting going. Once you’re underway, it’s plain sailing. And hey, you don’t have to type up all your findings like I do 🙂