The spring boot @Async annotation is used to execute a method asynchronously. This means that the @Async annotation helps to invoke a method and forget about it. The call method will not wait for the completion or response of the asynchronous method. This improves the efficiency of the application by running the process in parallel in the background.

Since the spring boot application will not wait for the response, the return data type of the @Async method is void or Future. This may be used to track the response of the asynchronous method. The asynchronous method can be introduced in the spring boot framework using two basic annotations: @EnableAsync and @Async.

The following examples help to incorporate the asynchronous method and troubleshoot the asynchronous methods



Method execution without @Async annotation

The spring boot methods will run sequentially. If a method is called from another method, the call method will wait for the method to be completed and wait for the method to respond. All methods run in a single threaded model. Methods are performed one by one. This will affect the performance of the application if there is a parallel execution opportunity.

package com.yawintutor;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootAsyncApplication {
	public static void main(String[] args) {
		SpringApplication.run(SpringBootAsyncApplication.class, args);
	}
}
package com.yawintutor;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

	@Autowired
	TestService testService;
	
	@RequestMapping(value = "/welcome", method = RequestMethod.GET)
	public String welcome() {
		for(int i=0; i<10; i++) {
			testService.print(i);
		}
		return "success";
	}
}
package com.yawintutor;

import org.springframework.stereotype.Service;

@Service
public class TestService {
	public void print(int index) {
		System.out.println("start: " + Thread.currentThread().getName() + ", index : " + index);
		try { Thread.sleep(5000);} catch(Exception e) {}
		System.out.println("end  : " + Thread.currentThread().getName() + ", index : " + index);
	}
}

Output

The method will execute sequencially using a single thread. The output will be as like below. Open the browser and call the url http://localhost:8080/welcome

start: http-nio-8080-exec-1, index : 0
end  : http-nio-8080-exec-1, index : 0
start: http-nio-8080-exec-1, index : 1
end  : http-nio-8080-exec-1, index : 1
start: http-nio-8080-exec-1, index : 2
end  : http-nio-8080-exec-1, index : 2
......................................
start: http-nio-8080-exec-1, index : 9
end  : http-nio-8080-exec-1, index : 9


Solution 1 – With Annotation @EnableAsync and @Async

The spring boot annotations @EnableAsync and @Async are used to create an asynchronous method. The @EnableAsync annotation is applied to the main method class and the @Async annotation is added to the method that has to be implemented asynchronously.

package com.yawintutor;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync
public class SpringBootAsyncApplication {
	public static void main(String[] args) {
		SpringApplication.run(SpringBootAsyncApplication.class, args);
	}
}
package com.yawintutor;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

	@Autowired
	TestService testService;
	
	@RequestMapping(value = "/welcome", method = RequestMethod.GET)
	public String welcome() {
		for(int i=0; i<10; i++) {
			testService.print(i);
		}
		return "success";
	}
}
package com.yawintutor;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class TestService {

	@Async
	public void print(int index) {
		System.out.println("start: " + Thread.currentThread().getName() + ", index : " + index);
		try { Thread.sleep(5000);} catch(Exception e) {}
		System.out.println("end  : " + Thread.currentThread().getName() + ", index : " + index);
	}
}

Output

The method print in the TestService is in started parallel. The below log shows that the method started in parallel. Open a browser and call the url http://localhost:8080/welcome

start: task-1, index : 0
start: task-3, index : 2
start: task-4, index : 3
start: task-5, index : 4
start: task-6, index : 5
start: task-7, index : 6
start: task-8, index : 7
start: task-2, index : 1
end  : task-7, index : 6
end  : task-6, index : 5
start: task-6, index : 9
end  : task-4, index : 3
end  : task-3, index : 2
end  : task-2, index : 1
end  : task-5, index : 4
end  : task-8, index : 7
end  : task-1, index : 0
start: task-7, index : 8
end  : task-7, index : 8
end  : task-6, index : 9


Solution 2 – Limit the thread execution

The spring boot annotation @Async allows to execute the method asynchronously. By default, eight threads can be used to run the method in parallel. The configuration of the thread pool size helps to maximise or reduce the number of thread executions in the @Async process. If the thread number is high, the OutOfMemoryError will be thrown into the application. If the thread number is less, the application will take longer to complete or the performance will be less.

application.properties

spring.task.execution.pool.core-size=10

Output

All the method calls will execute in parallel. The output will be as like below. Call the url in a browser http://localhost:8080/welcome

start: task-1, index : 0
start: task-4, index : 3
start: task-9, index : 8
start: task-5, index : 4
start: task-8, index : 7
start: task-7, index : 6
start: task-10, index : 9
start: task-6, index : 5
start: task-2, index : 1
start: task-3, index : 2
end  : task-9, index : 8
end  : task-1, index : 0
end  : task-3, index : 2
end  : task-2, index : 1
end  : task-7, index : 6
end  : task-5, index : 4
end  : task-10, index : 9
end  : task-8, index : 7
end  : task-6, index : 5
end  : task-4, index : 3


Solution 3 – Limit the Queue size

If the spring boot @Async method accepts calls more than the core size of the pool, the calls would be queued in the list. The size of the queue is the maximum value of the integer. As a consequence, all the calls are queued up and the application hangs. The queue size property helps to restrict the size of the queue, and if something above the size is thrown as an exception. java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@68513a76 rejected from java.util.concurrent.ThreadPoolExecutor@18a975cd[Running, pool size = 4, active threads = 4, queued tasks = 4, completed tasks = 0]

application.properties

spring.task.execution.pool.core-size=4
spring.task.execution.pool.queue-capacity=6

Output

Only 4 threads are used to execute the asynchronous method. Rest all queued and executed later.

start: task-1, index : 0
start: task-2, index : 1
start: task-3, index : 2
start: task-4, index : 3
end  : task-3, index : 2
end  : task-4, index : 3
end  : task-1, index : 0
start: task-1, index : 6
end  : task-2, index : 1
start: task-4, index : 5
start: task-3, index : 4
start: task-2, index : 7
end  : task-4, index : 5
end  : task-2, index : 7
end  : task-3, index : 4
end  : task-1, index : 6
start: task-2, index : 9
start: task-4, index : 8
end  : task-2, index : 9
end  : task-4, index : 8


Solution 4 – create additional threads on high load

If the size of the queue is configured in the application and the number of calls are raised by more than the size of the queue, the async method will throw the exception. The configuration maximum pool size tends to raise the number of threads if the number of calls is greater than the size of the queue. The configuration below helps maximise the number of threads when the load is heavy.

application.properties

spring.task.execution.pool.core-size=4
spring.task.execution.pool.max-size=6
spring.task.execution.pool.queue-capacity=4

Output

The first 4 calls (index 0-3) are executed using the core threads (task-1 to 4). The next 4 calls are queued in the list (index 4-7). The remaining two calls ( index 8-9) are executed using the additional threads (task-5 to 6).

start: task-5, index : 8
start: task-2, index : 1
start: task-4, index : 3
start: task-1, index : 0
start: task-3, index : 2
start: task-6, index : 9
end  : task-4, index : 3
end  : task-1, index : 0
start: task-1, index : 5
end  : task-2, index : 1
start: task-2, index : 6
end  : task-5, index : 8
start: task-5, index : 7
start: task-4, index : 4
end  : task-3, index : 2
end  : task-6, index : 9
end  : task-1, index : 5
end  : task-5, index : 7
end  : task-2, index : 6
end  : task-4, index : 4


Solution 5 – Customize the thread pool

The application.properties configuration will apply to all @Async methods in the spring boot application. The thread pool can be customised to a single @Async method. The example below shows how to customise a thread pool to an asynchronous method.

package com.yawintutor;

import java.util.concurrent.Executor;

import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

@Service
public class TestService {

	@Async("ThreadPoolTaskExecutorBean")
	public void print(int index) {
		System.out.println("start: " + Thread.currentThread().getName() + ", index : " + index);
		try { Thread.sleep(5000);} catch(Exception e) {}
		System.out.println("end  : " + Thread.currentThread().getName() + ", index : " + index);
	}
	
	@Bean(name = "ThreadPoolTaskExecutorBean")
	public Executor getAsyncExecutor() {
		int corePoolSize = 3;
		int maxPoolSize = 5;
		int queueCapacity = 5;

		ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
		executor.setCorePoolSize(corePoolSize);
		executor.setMaxPoolSize(maxPoolSize);
		executor.setQueueCapacity(queueCapacity);
		executor.initialize();
		return executor;
	}
}

Output

start: ThreadPoolTaskExecutorBean-1, index : 0
start: ThreadPoolTaskExecutorBean-2, index : 1
start: ThreadPoolTaskExecutorBean-3, index : 2
start: ThreadPoolTaskExecutorBean-4, index : 8
start: ThreadPoolTaskExecutorBean-5, index : 9
end  : ThreadPoolTaskExecutorBean-4, index : 8
end  : ThreadPoolTaskExecutorBean-5, index : 9
end  : ThreadPoolTaskExecutorBean-3, index : 2
end  : ThreadPoolTaskExecutorBean-1, index : 0
end  : ThreadPoolTaskExecutorBean-2, index : 1
start: ThreadPoolTaskExecutorBean-1, index : 6
start: ThreadPoolTaskExecutorBean-3, index : 5
start: ThreadPoolTaskExecutorBean-5, index : 4
start: ThreadPoolTaskExecutorBean-4, index : 3
start: ThreadPoolTaskExecutorBean-2, index : 7
end  : ThreadPoolTaskExecutorBean-3, index : 5
end  : ThreadPoolTaskExecutorBean-5, index : 4
end  : ThreadPoolTaskExecutorBean-2, index : 7
end  : ThreadPoolTaskExecutorBean-4, index : 3
end  : ThreadPoolTaskExecutorBean-1, index : 6



Leave a Reply