#
@Async annotation in Spring Boot
This tutorial explains how we can use @Async annotation in Java Spring Boot.
@Async annotation
in Spring Boot is used for telling Spring to run that method on another thread, so in parallel
with the parent thread.
In order to use @Async annotation we need to:
1) Enable Async Support for the application
This is done by adding @EnableAsync
at the main application class or in the configuration class which is used
to start the application.
2) Add the @Async annotation at the method level
Please take a look at the following example and read carefully the comments. The code is self-explanatory.
This example is created from a simple Spring Boot application created with Spring Initializr. I am using Maven, Java 17, Spring Boot 3.1.0.
From the base application downloaded from Spring Initializr, I updated the main class and I added some new classes as below:
package com.example.demo;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class MyService1 {
@Async
// This method runs Async but not value is returned
public void myMethod() throws InterruptedException {
System.out.println(">>>>> MyService1 - start ... Thread="+Thread.currentThread().getName());
Thread.sleep(2000);
System.out.println(">>>>> MyService1 - end ... Thread="+Thread.currentThread().getName());
}
}
package com.example.demo;
import org.springframework.stereotype.Service;
@Service
public class MyService2 {
// This method runs in synchronous mode
public void myMethod() throws InterruptedException {
System.out.println(">>>>> MyService2 - start ... Thread="+Thread.currentThread().getName());
Thread.sleep(4000);
System.out.println(">>>>> MyService2 - end ... Thread="+Thread.currentThread().getName());
}
}
package com.example.demo;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture;
@Service
public class MyService3 {
// This method runs Async but, and return a value
@Async("completableFutureTaskExecutor")
public CompletableFuture<String> myMethod() throws InterruptedException {
System.out.println(">>>>> MyService3 - start ... Thread="+Thread.currentThread().getName());
Thread.sleep(4000);
System.out.println(">>>>> MyService3 - end ... Thread="+Thread.currentThread().getName());
var returnVal = "Value returned by MyService3";
return CompletableFuture.completedFuture(returnVal);
}
}
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
@SpringBootApplication
@EnableAsync
public class DemoApplication {
@Bean
@Primary
// Define an executor for the ASYNC tasks
// This is not mandatory, but it is useful in PROD to establish some limits
public Executor defaultTaskExecutor1() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setQueueCapacity(100);
executor.setCorePoolSize(2);
executor.setMaxPoolSize(3);
executor.setThreadNamePrefix("DemoApp-");
executor.initialize();
return executor;
}
@Bean
// Define an executor for the ASYNC tasks
// This is not mandatory, but it is useful in PROD to establish some limits
public Executor completableFutureTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setQueueCapacity(100);
executor.setCorePoolSize(2);
executor.setMaxPoolSize(3);
executor.setThreadNamePrefix("CF-");
executor.initialize();
return executor;
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
// Create the application context and start Spring Boot
ApplicationContext appContext = SpringApplication.run(DemoApplication.class, args);
System.out.println("DemoApplication - start");
// Get a Bean fron the application context and run a method
MyService1 myService1 = appContext.getBean(MyService1.class);
MyService2 myService2 = appContext.getBean(MyService2.class);
MyService3 myService3 = appContext.getBean(MyService3.class);
System.out.println("MAIN Thread="+Thread.currentThread().getName());
myService1.myMethod();
myService2.myMethod();
CompletableFuture<String> cf = myService3.myMethod();
cf.get();
System.out.println("DemoApplication - end");
}
}
When I run this code I get the following log:
DemoApplication - start
MAIN Thread=main
>>>>> MyService2 - start ... Thread=main
>>>>> MyService1 - start ... Thread=DemoApp-1
>>>>> MyService1 - end ... Thread=DemoApp-1
>>>>> MyService2 - end ... Thread=main
>>>>> MyService3 - start ... Thread=CF-1
>>>>> MyService3 - end ... Thread=CF-1
DemoApplication - end
Info
- The Spring Framework provides abstractions for the asynchronous execution and scheduling of tasks with the
TaskExecutor
andTaskScheduler
interfaces, respectively. - Spring’s TaskExecutor interface is identical to the java.util.concurrent.Executor interface.
- Spring includes a number of pre-built implementations of TaskExecutor, such as:
SyncTaskExecutor
,ThreadPoolTaskExecutor
,ConcurrentTaskExecutor
,ThreadPoolTaskExecutor
.
In any Java applications we have:
- Executor interface - we have
void execute(Runnable command)
method => you can run Async call in a thread pool - ExecutorService interface extends Executor and add more method, including "submit()", "invokeAny()", "shutdown()". More information we can have here
- Executors class - a factory and utility methods for Executor, ExecutorService, ScheduledExecutorService, ThreadFactory, and Callable classes defined in this package. The most used methods perhaps are: Executors.newSingleThreadExecutor(), Executors.newFixedThreadExecutor(), Executors.newScheduledThreadExecutor(). More information you can have here.
In Spring Boot applications "taskExecutor" Bean is the default TaskExecutor. If not defined, and we have more than 1 task executors we need to define one as @Primary to be the default task executor.
If no executor is created one will be created automatically.