mn create-app example.micronaut.micronautguide --build=maven --lang=kotlin
Schedule periodic tasks inside your Micronaut applications
Learn how to schedule periodic tasks inside your Micronaut microservices.
Authors: Sergio del Amo
Micronaut Version: 4.6.3
1. Getting Started
In this guide, we will create a Micronaut application written in Kotlin.
Nowadays it is pretty usual to have some kind of cron or scheduled task that needs to run every midnight, every hour, a few times a week,…
In this guide you will learn how to use Micronaut capabilities to schedule periodic tasks inside a Micronaut microservice.
2. What you will need
To complete this guide, you will need the following:
-
Some time on your hands
-
A decent text editor or IDE (e.g. IntelliJ IDEA)
-
JDK 21 or greater installed with
JAVA_HOME
configured appropriately
3. Solution
We recommend that you follow the instructions in the next sections and create the application step by step. However, you can go right to the completed example.
-
Download and unzip the source
4. Writing the Application
Create an application using the Micronaut Command Line Interface or with Micronaut Launch.
If you don’t specify the --build argument, Gradle with the Kotlin DSL is used as the build tool. If you don’t specify the --lang argument, Java is used as the language.If you don’t specify the --test argument, JUnit is used for Java and Kotlin, and Spock is used for Groovy.
|
The previous command creates a Micronaut application with the default package example.micronaut
in a directory named micronautguide
.
4.1. Set log level INFO for package demo
In this guide, we will use several log statements to show Job execution.
Add this statement to the end of logback.xml
:
<logger name="example.micronaut" level="INFO"/>
The above line configures a logger for package example.micronaut
with log level INFO.
4.2. Creating a Job
Create the HelloWorldJob
class:
package example.micronaut
import io.micronaut.scheduling.annotation.Scheduled
import org.slf4j.LoggerFactory
import java.text.SimpleDateFormat
import java.util.Date
import jakarta.inject.Singleton
@Singleton (1)
class HelloWorldJob {
@Scheduled(fixedDelay = "10s") (3)
fun executeEveryTen() {
LOG.info("Simple Job every 10 seconds: {}", SimpleDateFormat("dd/M/yyyy hh:mm:ss").format(Date()))
}
@Scheduled(fixedDelay = "45s", initialDelay = "5s") (4)
fun executeEveryFourtyFive() {
LOG.info("Simple Job every 45 seconds: {}", SimpleDateFormat("dd/M/yyyy hh:mm:ss").format(Date()))
}
companion object {
private val LOG = LoggerFactory.getLogger(HelloWorldJob::class.java) (2)
}
}
1 | Use jakarta.inject.Singleton to designate a class as a singleton. |
2 | Inject a Logger. |
3 | Create trigger every 10 seconds |
4 | Create another trigger every 45 seconds with an initial delay of 5 seconds (5000 millis) |
Now start the application. Execute the ./mvnw mn:run
command, which will start the application on port 8080.
After a few seconds, you will see the following output:
... Simple Job every 10 seconds :15/5/2018 12:48:02 (1)
... Simple Job every 45 seconds :15/5/2018 12:48:07 (2)
... Simple Job every 10 seconds :15/5/2018 12:48:12 (3)
... Simple Job every 10 seconds :15/5/2018 12:48:22
... Simple Job every 10 seconds :15/5/2018 12:48:32
... Simple Job every 10 seconds :15/5/2018 12:48:42
... Simple Job every 45 seconds :15/5/2018 12:48:52 (4)
... Simple Job every 10 seconds :15/5/2018 12:48:52
1 | First execution of 10 seconds Job after the application starts |
2 | The 45 seconds Job starts 5 seconds after the application starts |
3 | Second execution of 10 seconds Job 10 seconds after the first execution |
4 | Second execution of 45 seconds Job 45 seconds after the first execution |
4.3. Business logic in dedicated Use Cases
Although the previous example is valid, usually you don’t want to put your business logic in a Job. A better approach is
to create an additional bean that the Job
invokes. This approach decouples your business logic from the scheduling logic.
Moreover, it facilitates testing and maintenance. Let’s see an example:
Create the following use case:
package example.micronaut
import org.slf4j.LoggerFactory
import java.text.SimpleDateFormat
import java.util.Date
import jakarta.inject.Singleton
@Singleton
class EmailUseCase {
fun send(user: String, message: String) {
LOG.info("Sending email to {} : {} at {}", user, message, SimpleDateFormat("dd/M/yyyy hh:mm:ss").format(Date()))
}
companion object {
private val LOG = LoggerFactory.getLogger(EmailUseCase::class.java)
}
}
And then the Job:
package example.micronaut
import io.micronaut.scheduling.annotation.Scheduled
import jakarta.inject.Singleton
@Singleton (1)
class DailyEmailJob(protected val emailUseCase: EmailUseCase) { (2)
@Scheduled(cron = "0 30 4 1/1 * ?") (3)
fun execute() {
emailUseCase.send("john.doe@micronaut.example", "Test Message") (4)
}
}
1 | Use jakarta.inject.Singleton to designate a class as a singleton. |
2 | Constructor injection. |
3 | Trigger the Job once a day at 04:30 AM |
4 | Call the injected use case |
4.4. Scheduling a Job Manually
Consider the following scenario. You want to send every user an email two hours after they register on your application and ask them about their experiences during this first interaction.
For this guide, we will schedule a Job to trigger after one minute.
To test it, we will call a new use case named RegisterUseCase
twice when the application starts.
Modify Application.kt
:
package example.micronaut
import io.micronaut.context.event.ApplicationEventListener
import io.micronaut.runtime.Micronaut.build
import io.micronaut.runtime.server.event.ServerStartupEvent
import jakarta.inject.Singleton
fun main(args: Array<String>) {
build()
.args(*args)
.packages("example.micronaut")
.start()
}
@Singleton (1)
class Application(val registerUseCase: RegisterUseCase) (2)
: ApplicationEventListener<ServerStartupEvent> { (3)
override fun onApplicationEvent(event: ServerStartupEvent) { (4)
try {
registerUseCase.register("harry@micronaut.example")
Thread.sleep(20000)
registerUseCase.register("ron@micronaut.example")
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
}
1 | Use jakarta.inject.Singleton to designate a class as a singleton. |
2 | Listen to the event ServerStartupEvent |
3 | Constructor injection of RegisterUseCase |
4 | onApplicationEvent is invoked when the application starts. Subscribing to an event is as easy as implementing ApplicationEventListener . |
Create a runnable task EmailTask.kt
package example.micronaut
class EmailTask(private val emailUseCase: EmailUseCase, private val email: String, private val message: String) : Runnable {
override fun run() {
emailUseCase.send(email, message)
}
}
Create RegisterUseCase.kt
, which schedules the previous task.
package example.micronaut
import io.micronaut.scheduling.TaskExecutors
import io.micronaut.scheduling.TaskScheduler
import org.slf4j.LoggerFactory
import java.text.SimpleDateFormat
import java.time.Duration
import java.util.Date
import jakarta.inject.Named
import jakarta.inject.Singleton
@Singleton
class RegisterUseCase(private val emailUseCase: EmailUseCase, (1)
@param:Named(TaskExecutors.SCHEDULED) private val taskScheduler: TaskScheduler) { (2)
fun register(email: String) {
LOG.info("saving {} at {}", email, SimpleDateFormat("dd/M/yyyy hh:mm:ss").format(Date()))
scheduleFollowupEmail(email, "Welcome to the Micronaut framework")
}
private fun scheduleFollowupEmail(email: String, message: String) {
val task = EmailTask(emailUseCase, email, message) (3)
taskScheduler.schedule(Duration.ofMinutes(1), task) (4)
}
companion object {
private val LOG = LoggerFactory.getLogger(RegisterUseCase::class.java)
}
}
1 | Constructor injection of EmailUseCase |
2 | Inject the TaskScheduler bean |
3 | Create a Runnable task |
4 | Schedule the task to run a minute from now |
If you execute the above code, you will see the Job being executed one minute after we schedule it and with the supplied email address.
INFO example.micronaut.RegisterUseCase - saving harry@micronaut.example at 15/5/2018 06:25:14
INFO example.micronaut.RegisterUseCase - saving ron@micronaut.example at 15/5/2018 06:25:34
INFO example.micronaut.EmailUseCase - Sending email to harry@micronaut.example : Welcome to Micronaut at 15/5/2018 06:26:14
INFO example.micronaut.EmailUseCase - Sending email to ron@micronaut.example : Welcome to the Micronaut framework at 15/5/2018 06:26:34
5. Summary
During this guide, we learned how to configure Jobs using the @Scheduled
annotation using fixedDelay, initialDelay,
and cron, as well as manually configuring Jobs with a TaskScheduler
and tasks.
6. Generate a Micronaut Application Native Executable with GraalVM
We will use GraalVM, the polyglot embeddable virtual machine, to generate a native executable of our Micronaut application.
Compiling native executables ahead of time with GraalVM improves startup time and reduces the memory footprint of JVM-based applications.
Only Java and Kotlin projects support using GraalVM’s native-image tool. Groovy relies heavily on reflection, which is only partially supported by GraalVM.
|
6.1. GraalVM installation
sdk install java 21.0.5-graal
For installation on Windows, or for manual installation on Linux or Mac, see the GraalVM Getting Started documentation.
The previous command installs Oracle GraalVM, which is free to use in production and free to redistribute, at no cost, under the GraalVM Free Terms and Conditions.
Alternatively, you can use the GraalVM Community Edition:
sdk install java 21.0.2-graalce
6.2. Native executable generation
To generate a native executable using Maven, run:
./mvnw package -Dpackaging=native-image
The native executable is created in the target
directory and can be run with target/micronautguide
.
Execute the native executable, and you will see the log message of the Jobs appear:
10:16:20.803 [pool-2-thread-1] INFO example.micronaut.HelloWorldJob - Simple Job every 10 seconds: 09/12/2019 10:16:20
10:16:20.806 [main] INFO example.micronaut.RegisterUseCase - saving harry@micronaut.example at 09/12/2019 10:16:20
10:16:25.804 [pool-2-thread-2] INFO example.micronaut.HelloWorldJob - Simple Job every 45 seconds: 09/12/2019 10:16:25
10:16:30.807 [pool-2-thread-4] INFO example.micronaut.HelloWorldJob - Simple Job every 10 seconds: 09/12/2019 10:16:30
7. Next steps
Explore more features with Micronaut Guides.
8. Help with the Micronaut Framework
The Micronaut Foundation sponsored the creation of this Guide. A variety of consulting and support services are available.
9. License
All guides are released with an Apache license 2.0 license for the code and a Creative Commons Attribution 4.0 license for the writing and media (images…). |