1. Overview

The goal of Spring Cloud Task is to provide the functionality of creating short-lived microservices for Spring Boot application.

In Spring Cloud Task, we’ve got the flexibility of running any task dynamically, allocating resources on demand and retrieving the results after the Task completion.

Tasks is a new primitive within Spring Cloud Data Flow allowing users to execute virtually any Spring Boot application as a short-lived task.

2. Developing a Simple Task Application

2.1. Adding Relevant Dependencies

To start, we can add dependency management section with spring-cloud-task-dependencies:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-task-dependencies</artifactId>
            <version>2.2.3.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

This dependency management manages versions of dependencies through the import scope.

We need to add the following dependencies:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-task</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-task-core</artifactId>
</dependency>

This is the link to the Maven Central of spring-cloud-task-core.

Now, to start our Spring Boot application, we need spring-boot-starter with the relevant parent.

We’re going to use Spring Data JPA as an ORM tool, so we need to add the dependency for that as well:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
    <version>2.6.1</version>
</dependency>

The details of bootstrapping a simple Spring Boot application with Spring Data JPA are available here.

We can check the newest version of the spring-boot-starter-parent on Maven Central.

2.2. The @EnableTask Annotation

To bootstrap the functionality of Spring Cloud Task, we need to add @EnableTask annotation:

@SpringBootApplication
@EnableTask
public class TaskDemo {
    // ...
}

The annotation brings SimpleTaskConfiguration class in the picture which in turns registers the TaskRepository and its infrastructure. By default, an in-memory map is used to store the status of the TaskRepository.

The primary information of TaskRepository is modeled in TaskExecution class. The noted fields of this class are taskName, startTime, endTime, exitMessage. The exitMessage stores the available information at the exit time.

If an exit is caused by a failure in any event of the application, the complete exception stack trace will be stored here.

Spring Boot provides an interface ExitCodeExceptionMapper which maps uncaught exceptions to exit codes allowing scrutinized debug. The Cloud Task stores the information in the data source for future analysis.

2.3. Configuring a DataSource for TaskRepository

The in-memory map to store the TaskRepository will vanish once the task ends and we’ll lose data related to Task events. To store in a permanent storage, we’re going to use MySQL as a data source with Spring Data JPA.

The data source is configured in application.yml file. To configure Spring Cloud Task to use the provided data source as an storage of TaskRepository, we need to create a class that extends DefaultTaskConfigurer.

Now, we can send configured Datasource as a constructor argument to the superclass’ constructor:

@Autowired
private DataSource dataSource;

public class HelloWorldTaskConfigurer extends DefaultTaskConfigurer{
    public HelloWorldTaskConfigurer(DataSource dataSource){
        super(dataSource);
    }
}

To have the above configuration in action, we need to annotate an instance of DataSource with @Autowired annotation and inject the instance as constructor-argument of a HelloWorldTaskConfigurer bean defined above:

@Bean
public HelloWorldTaskConfigurer getTaskConfigurer() {
    return new HelloWorldTaskConfigurer(dataSource);
}

This completes the configuration to store TaskRepository to MySQL database.

2.4. Implementation

In Spring Boot, we can execute any Task just before application finishes its startup. We can use ApplicationRunner or CommandLineRunner interfaces to create a simple Task.

We need to implement the run method of these interfaces and declare the implementing class as a bean:

@Component
public static class HelloWorldApplicationRunner 
  implements ApplicationRunner {
 
    @Override
    public void run(ApplicationArguments arg0) throws Exception {
        System.out.println("Hello World from Spring Cloud Task!");
    }
}

Now, if we run our application, we should get our task producing necessary output with required tables created in our MySQL database logging the event data of the Task.

3. Life-cycle of a Spring Cloud Task

In the beginning, we create an entry in the TaskRepository. This’s the indication that all beans are ready to be used in the Application and the run method of Runner interface is ready to be executed.

Upon completion of the execution of the run method or in any failure of ApplicationContext event, TaskRepository will be updated with another entry.

During the task life-cycle, we can register listeners available from TaskExecutionListener interface. We need a class implementing the interface having three methods – onTaskEnd, onTaksFailed and onTaskStartup triggered in respective events of the Task.

We need to declare the bean of the implementing class in our TaskDemo class:

@Bean
public TaskListener taskListener() {
    return new TaskListener();
}

4. Integration With Spring Batch

We can execute Spring Batch Job as a Task and log events of the Job execution using Spring Cloud Task. To enable this feature we need to add Batch dependencies pertaining to Boot and Cloud:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-task-batch</artifactId>
</dependency>

Here is the link to the Maven Central of spring-cloud-task-batch.

To configure a job as a Task we need to have the Job bean registered in the JobConfiguration class:

@Bean
public Job job2() {
    return jobBuilderFactory.get("job2")
      .start(stepBuilderFactory.get("job2step1")
      .tasklet(new Tasklet(){
          @Override
          public RepeatStatus execute(
            StepContribution contribution,
            ChunkContext chunkContext) throws Exception {
            System.out.println("This job is from Baeldung");
                return RepeatStatus.FINISHED;
          }
    }).build()).build();
}

We need to decorate the TaskDemo class with @EnableBatchProcessing annotation:

//..Other Annotation..
@EnableBatchProcessing
public class TaskDemo {
    // ...
}

The @EnableBatchProcessing annotation enables Spring Batch features with a base configuration required to set up batch jobs.

Now, if we run the application, the @EnableBatchProcessing annotation will trigger the Spring Batch Job execution and Spring Cloud Task will log the events of the executions of all batch jobs with the other Task executed in the springcloud database.

5. Launching a Task from Stream

We can trigger Tasks from Spring Cloud Stream. To serve this purpose, we have the @EnableTaskLaucnher annotation. Once, we add the annotation with Spring Boot app, a TaskSink will be available:

@SpringBootApplication
@EnableTaskLauncher
public class StreamTaskSinkApplication {
    public static void main(String[] args) {
        SpringApplication.run(TaskSinkApplication.class, args);
    }
}

The TaskSink receives the message from a stream that contains a GenericMessage containing TaskLaunchRequest as a payload. Then it triggers a Task-based on co-ordinate provided in the Task launch request.

To have TaskSink functional, we require a bean configured that implements TaskLauncher interface. For testing purpose, we’re mocking the implementation here:

@Bean
public TaskLauncher taskLauncher() {
    return mock(TaskLauncher.class);
}

We need to note here that the TaskLauncher interface is only available after adding the spring-cloud-deployer-local dependency:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-deployer-local</artifactId>
    <version>2.3.1.RELEASE</version>
</dependency>

We can test whether the Task launched by invoking input of the Sink interface:

public class StreamTaskSinkApplicationTests {
   
    @Autowired
    private Sink sink; 
    
    //
}

Now, we create an instance of TaskLaunchRequest and send that as a payload of GenericMessage<TaskLaunchRequest> object. Then we can invoke the input channel of the Sink keeping the GenericMessage object in the channel.

6. Conclusion

In this tutorial, we’ve explored how Spring Cloud Task performs and how to configure it to log its events in a database. We also observed how Spring Batch job is defined and stored in the TaskRepository. Lastly, we explained how we can trigger Task from within Spring Cloud Stream.

As always, the code is available over on GitHub.