Schedule periodic tasks inside your Micronaut applications

Learn how to schedule periodic tasks inside your Micronaut microservices.

Authors: Sergio del Amo

Micronaut Version: 1.0.0

1 Getting Started

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 native capabilities to schedule periodic tasks inside a Micronaut microservice.

1.1 What you will need

To complete this guide, you will need the following:

  • Some time on your hands

  • A decent text editor or IDE

  • JDK 1.8 or greater installed with JAVA_HOME configured appropriately

1.2 Solution

We recommend you to follow the instructions in the next sections and create the app step by step. However, you can go right to the completed example.

or

Then, cd into the complete folder which you will find in the root project of the downloaded/cloned project.

2 Writing the Application

Create a Kotlin Micronaut app using the Micronaut Command Line Interface.

mn create-app example.micronaut.complete --features=kotlin

The previous command createas a micronaut app with the default package example.micronaut in a folder named complete.

Due to the --features kotlin flag, it generates a Kotlin Micronaut app and it uses Gradle build system. However, you could use other build tool such as Maven or other programming languages such as Java or Groovy.

If you are using Java or Kotlin and IntelliJ IDEA make sure you have enabled annotation processing.

annotationprocessorsintellij

Kotlin, Kapt and IntelliJ

As of this writing IntelliJ’s built-in compiler does not directly support Kapt and annotation processing. You must instead configure Intellij to run Gradle (or Maven) compilation as a build step before running your tests or application class.

First edit the run configuration for tests or for the application and select "Run Gradle task" as a build step:

Intellij Settings

Then add the classes task as task to execute for the application or for tests the testClasses task:

Intellij Settings

Now whenever you run tests or the application Micronaut classes will be generated at compilation time.

Read Micronaut Kotlin section to learn more.

Alternatively, you can delegate IntelliJ build/run actions to gradle completely:

delegatetogradle

2.1 Set log level INFO for package demo

During this guide, we are going to use several log statements to show Job execution.

Add at the end of logback.xml the next statement:

src/main/resources/logback.xml
<configuration>
...
..
.
    <logger name="example.micronaut" level="info" />
</configuration>

The above line configures a logger for package example.micronaut with log level INFO.

2.2 Creating a Job

Create a file and add the following:

src/main/kotlin/example/micronaut/HelloWorldJob.kt
package example.micronaut

import io.micronaut.scheduling.annotation.Scheduled
import org.slf4j.LoggerFactory

import javax.inject.Singleton
import java.text.SimpleDateFormat
import java.util.Date

@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 To register a Singleton in Micronaut’s application context annotate your class with javax.inject.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:

./gradlew run

And 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 just after the application starts
2 The 45 seconds job just starts 5 seconds after the app starts
3 Second execution of 10 seconds job just 10 seconds after the first execution
4 Second execution of 45 seconds job just 45 seconds after the first execution

2.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 which 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:

src/main/kotlin/example/micronaut/EmailUseCase.kt
package example.micronaut

import org.slf4j.LoggerFactory

import javax.inject.Singleton
import java.text.SimpleDateFormat
import java.util.Date

@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) (2)
    }
}

And then the job:

src/main/kotlin/example/micronaut/DailyEmailJob.kt
package example.micronaut

import io.micronaut.scheduling.annotation.Scheduled
import javax.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 To register a Singleton in Micronaut’s application context annotate your class with javax.inject.Singleton
2 Constructor injection.
3 Trigger the job once a day at 04:30 AM
4 Call the injected use case

2.4 Scheduling a Job Manually

Imagine the following scenario. You want to send every user an email 2 hours after they registered into your app. You want to ask him about his experiences during this first interaction with your application.

For this guide, we are going to schedule a Job to trigger after one minute.

To test it out, we are going to call twice a new use case named RegisterUseCase when the app starts.

Create a bean, BootStrap.kt, which listens to the application startup event.

src/main/kotlin/example/micronaut/BootStrap.kt
package example.micronaut

import io.micronaut.context.event.ApplicationEventListener
import io.micronaut.runtime.server.event.ServerStartupEvent
import javax.inject.Singleton

@Singleton (1)
class BootStrap(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 Annotate BootStrap.kt with @Singleton to register it in the application context.
2 Constructor injection of RegisterUseCase
3 Listen to the event ServerStartupEvent
4 onApplicationEvent is invoked when the app starts.

 

As you see in the previous example, subscribing to an event is as easy as implementing ApplicationEventListener.

Create a runnable task EmailTask.kt

src/main/kotlin/example/micronaut/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 RegisterService.kt which schedules the previous task.

src/main/kotlin/example/micronaut/RegisterUseCase.kt
package example.micronaut

import io.micronaut.scheduling.TaskExecutors
import io.micronaut.scheduling.TaskScheduler
import org.slf4j.Logger
import org.slf4j.LoggerFactory

import javax.inject.Named
import javax.inject.Singleton
import java.text.SimpleDateFormat
import java.time.Duration
import java.util.Date

@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 Micronaut")
    }

    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 Micronaut at 15/5/2018 06:26:34

3 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.