Building @Async REST APIs with Spring @EnableAsync

Learn to create asynchronous controller methods in Spring framework with the help of @Async and @EnableAsync annotations, async thread pool on top of Java ExecutorService framework.

1. Spring @Async rest controller

Spring comes with @EnableAsync annotation and can be applied on application classes for asynchronous behavior. This annotation will look for methods marked with @Async annotation and run in background thread pools. The @Async annotated methods can return CompletableFuture to hold the result of an asynchronous computation.

To enable async configuration in spring, follow these steps:

  1. Create async thread pool

    @Configuration
    @EnableAsync
    public class AsynchConfiguration 
    {
    	@Bean(name = "asyncExecutor")
    	public Executor asyncExecutor() 
    	{
    		ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    		executor.setCorePoolSize(3);
    		executor.setMaxPoolSize(3);
    		executor.setQueueCapacity(100);
    		executor.setThreadNamePrefix("AsynchThread-");
    		executor.initialize();
    		return executor;
    	}
    }
    
  2. @Async controller methods

    Methods which shall run asynchronously, annotate them with @Async annotation and method return type should return

    @Async("asyncExecutor")
    public CompletableFuture<EmployeeNames> methodOne() throws InterruptedException {
    	//code
    }
    
  3. Combine async method results

    CompletableFuture.allOf(methodOne, methodTwo, methodThree).join();

2. Spring @Async rest controller example

In this demo, we will create an REST API which will fetch data from three (3) remote services asynchronously and when responses from all 3 services is available then aggregate the responses. e.g.

  1. Invoke EmployeeName API
  2. Invoke EmployeeAddress API
  3. Invoke EmployeePhone API
  4. Wait for responses from above services
  5. Aggregate all three API responses and build final response to send back to client

2.1. EmployeeName, EmployeeAddress and EmployeePhone APIs to be accessed async way

package com.howtodoinjava.example.sampleservice.controller;

import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.howtodoinjava.example.sampleservice.model.EmployeeAddress;
import com.howtodoinjava.example.sampleservice.model.EmployeeAddresses;
import com.howtodoinjava.example.sampleservice.model.EmployeeName;
import com.howtodoinjava.example.sampleservice.model.EmployeeNames;
import com.howtodoinjava.example.sampleservice.model.EmployeePhone;

@RestController
public class EmployeeDataController 
{
	private static Logger log = LoggerFactory.getLogger(EmployeeDataController.class);

	@RequestMapping(value = "/addresses", method = RequestMethod.GET)
	public EmployeeAddresses getAddresses() 
	{
		log.info("get addresses Start");

		EmployeeAddresses employeeAddressesList = new EmployeeAddresses();

		EmployeeAddress employeeAddress1 = new EmployeeAddress();
		EmployeeAddress employeeAddress2 = new EmployeeAddress();
		
		List<EmployeeAddress> addressList = new ArrayList<EmployeeAddress>();
		
		{
			employeeAddress1.setHouseNo("1111");
			employeeAddress1.setStreetNo("111");
			employeeAddress1.setZipCode("111111");
			
			employeeAddress2.setHouseNo("222");
			employeeAddress2.setStreetNo("222");
			employeeAddress2.setZipCode("222222");
			
			addressList.add(employeeAddress1);
			addressList.add(employeeAddress2);
			
			employeeAddressesList.setEmployeeAddressList(addressList);
		}

		return employeeAddressesList;
	}

	@RequestMapping(value = "/phones", method = RequestMethod.GET)
	public EmployeePhone getPhoneNumbers() 
	{
		log.info("get phones Start");

		EmployeePhone employeePhone = new EmployeePhone();
		{
			ArrayList<String> phoneNumberList = new ArrayList<String>();
			
			phoneNumberList.add("100000");
			phoneNumberList.add("200000");
			
			employeePhone.setPhoneNumbers(phoneNumberList);
		}

		return employeePhone;
	}

	@RequestMapping(value = "/names", method = RequestMethod.GET)
	public EmployeeNames getEmployeeName() 
	{
		log.info("get names Start");

		EmployeeNames employeeNamesList = new EmployeeNames();

		EmployeeName employeeName1 = new EmployeeName();
		EmployeeName employeeName2 = new EmployeeName();
		
		List<EmployeeName> employeeList = new ArrayList<EmployeeName>();
		{
			employeeName1.setFirstName("Santa");
			employeeName1.setLastName("Singh");
		}
		{
			employeeName2.setFirstName("Banta");
			employeeName2.setLastName("Singh");
		}

		employeeList.add(employeeName1);
		employeeList.add(employeeName2);

		employeeNamesList.setEmployeeNameList(employeeList);

		return employeeNamesList;
	}
}

2.2. Async thread pool configuration

import java.util.concurrent.Executor;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@Configuration
@EnableAsync
public class AsyncConfiguration 
{
	@Bean(name = "asyncExecutor")
	public Executor asyncExecutor() {
		ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
		executor.setCorePoolSize(3);
		executor.setMaxPoolSize(3);
		executor.setQueueCapacity(100);
		executor.setThreadNamePrefix("AsynchThread-");
		executor.initialize();
		return executor;
	}
}

2.3. Spring @Async controller methods

package com.howtodoinjava.example.async.service;

import java.util.concurrent.CompletableFuture;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import com.howtodoinjava.example.async.model.EmployeeAddresses;
import com.howtodoinjava.example.async.model.EmployeeNames;
import com.howtodoinjava.example.async.model.EmployeePhone;

@Service
public class AsyncService {

	private static Logger log = LoggerFactory.getLogger(AsyncService.class);

	@Autowired
	private RestTemplate restTemplate;

	@Bean
	public RestTemplate restTemplate() {
		return new RestTemplate();
	}

	@Async("asyncExecutor")
	public CompletableFuture<EmployeeNames> getEmployeeName() throws InterruptedException 
	{
		log.info("getEmployeeName starts");

		EmployeeNames employeeNameData = restTemplate.getForObject("http://localhost:8080/name", EmployeeNames.class);

		log.info("employeeNameData, {}", employeeNameData);
		Thread.sleep(1000L);	//Intentional delay
		log.info("employeeNameData completed");
		return CompletableFuture.completedFuture(employeeNameData);
	}

	@Async("asyncExecutor")
	public CompletableFuture<EmployeeAddresses> getEmployeeAddress() throws InterruptedException 
	{
		log.info("getEmployeeAddress starts");

		EmployeeAddresses employeeAddressData = restTemplate.getForObject("http://localhost:8080/address", EmployeeAddresses.class);

		log.info("employeeAddressData, {}", employeeAddressData);
		Thread.sleep(1000L);	//Intentional delay
		log.info("employeeAddressData completed");
		return CompletableFuture.completedFuture(employeeAddressData);
	}

	@Async("asyncExecutor")
	public CompletableFuture<EmployeePhone> getEmployeePhone() throws InterruptedException 
	{
		log.info("getEmployeePhone starts");

		EmployeePhone employeePhoneData = restTemplate.getForObject("http://localhost:8080/phone", EmployeePhone.class);

		log.info("employeePhoneData, {}", employeePhoneData);
		Thread.sleep(1000L);	//Intentional delay
		log.info("employeePhoneData completed");
		return CompletableFuture.completedFuture(employeePhoneData);
	}
}

2.4. Call async methods and aggregate results

package com.howtodoinjava.example.async.controller;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;

import com.howtodoinjava.example.async.model.EmployeeAddresses;
import com.howtodoinjava.example.async.model.EmployeeNames;
import com.howtodoinjava.example.async.model.EmployeePhone;
import com.howtodoinjava.example.async.service.AsyncService;

@RestController
public class AsyncController {

	private static Logger log = LoggerFactory.getLogger(AsyncController.class);

	@Autowired
	private AsyncService service;

	@RequestMapping(value = "/testAsynch", method = RequestMethod.GET)
	public void testAsynch() throws InterruptedException, ExecutionException 
	{
		log.info("testAsynch Start");

		CompletableFuture<EmployeeAddresses> employeeAddress = service.getEmployeeAddress();
		CompletableFuture<EmployeeNames> employeeName = service.getEmployeeName();
		CompletableFuture<EmployeePhone> employeePhone = service.getEmployeePhone();

		// Wait until they are all done
		CompletableFuture.allOf(employeeAddress, employeeName, employeePhone).join();
		
		log.info("EmployeeAddress--> " + employeeAddress.get());
		log.info("EmployeeName--> " + employeeName.get());
		log.info("EmployeePhone--> " + employeePhone.get());
	}
}

2.5. How to run the demo

Download and start both the applications.

Hit the API: http://localhost:8081/testAsynch.

Observe the output in console.

2.5.1. With @Aync Enabled
With Aync Enabled
With Aync Methods Enabled
2.5.2. Without Aync Enabled
Without Aync Methods Enabled
Without Aync Methods Enabled

Drop me your questions related to creating spring boot non blocking rest api.

Happy Learning !!

References:

https://spring.io/guides/gs/async-method/

Was this post helpful?

Join 7000+ Awesome Developers

Get the latest updates from industry, awesome resources, blog updates and much more.

* We do not spam !!

13 thoughts on “Building @Async REST APIs with Spring @EnableAsync”

  1. Hi
    Thanks for covering this.

    I want to add something, please share your viewpoint also.

    As you have made the rest endpoints async , than for better performance , you should also enable all dependent endpoints async and use of WebClient in async mode in place of rest template , will serve the purpose better.

    This will again leads to some more changes , i would request you to write another solution with proper async behaviour.

    Thanks Happy Learning

    Reply
  2. I am getting circular dependency exception

    org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘asyncService’: Bean with name ‘asyncService’ has been injected into other beans [restTemplate] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching – consider using ‘getBeanNamesForType’ with the ‘allowEagerInit’ flag turned off.

    Reply
  3. I was wondering if the example really works. I just tried to run both applications on a STS4 (Spring Boot 2 libs) but what I see is that the Employee API has entrypoints as /names /addresses /phones while the async clients will try to request /name /address /phone. As it is it fails with a 404 during the calls. It works if I change the entrypoints to be in singular, although the contents returned are a list and not individual items of course

    Reply
  4. If we uses same port number for /testAsynch as well as for /address .
    i have websphere application server and i am using portnumber 9080 for testAsynch and for address what should i used

    Reply
  5. Can you tell me how to extend Session timed out. i have one API it is working fine when it in local Machine but after deployment i am getting error Session and connection timeout it is happening because if you hit any https request the api should return response within 60 seconds but my Api is taking 5-10 minutes to execute task. so could you guide me how to increase this time limit. I am using Spring Boot 2.0 and Java 8

    Reply
    • You should change the design. In the first step, submit the task to be executed which will return success response if the server accepts the task. In the second step, poll the server to get task completion status after a fixed delay (e.g. every 30 seconds).

      Or better, the server shall return a JMS topic on task submission which the client will listen for task completion message.

      Reply
  6. Is it efficient to use the default task executor without having to configure one?
    You have asyncExecutor with certain config on thread pool numbers and so. I was not sure what values to specify that works best for our app.

    Reply
  7. Hi,
    Was Wondering when exactly we should use Async? If i understand correctly, this async is used in use cases where the user doesn’t want to wait for the response or the user wants quick response, by making synchronous calls in order to achieve one big response.

    But i was wondering if we can achieve retrying mechanism like with we do with JMS through this?

    Reply

Leave a Comment

HowToDoInJava

A blog about Java and related technologies, the best practices, algorithms, and interview questions.