Spring Boot @Async method in controller is executing synchronously

89,698

Solution 1

You are calling the @Async method from another method in the same class. Unless you enable AspectJ proxy mode for the @EnableAsync (and provide a weaver of course) that won't work (google "proxy self-invocation"). The easiest fix is to put the @Async method in another @Bean.

Solution 2

For all those who are still looking for all the steps in @Asnyc explained in a simple way, here is the answer:

Here is a simple example with @Async. Follow these steps to get @Async to work in your Spring Boot application:

Step 1: Add @EnableAsync annotation and Add TaskExecutor Bean to Application Class.

Example:

@SpringBootApplication
@EnableAsync
public class AsynchronousSpringBootApplication {

    private static final Logger logger = LoggerFactory.getLogger(AsynchronousSpringBootApplication.class);

    @Bean(name="processExecutor")
    public TaskExecutor workExecutor() {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setThreadNamePrefix("Async-");
        threadPoolTaskExecutor.setCorePoolSize(3);
        threadPoolTaskExecutor.setMaxPoolSize(3);
        threadPoolTaskExecutor.setQueueCapacity(600);
        threadPoolTaskExecutor.afterPropertiesSet();
        logger.info("ThreadPoolTaskExecutor set");
        return threadPoolTaskExecutor;
    }

    public static void main(String[] args) throws Exception {
  SpringApplication.run(AsynchronousSpringBootApplication.class,args);
 }
}

Step 2: Add Method which executes an Asynchronous Process

@Service
public class ProcessServiceImpl implements ProcessService {

    private static final Logger logger = LoggerFactory.getLogger(ProcessServiceImpl.class);

    @Async("processExecutor")
    @Override
    public void process() {
        logger.info("Received request to process in ProcessServiceImpl.process()");
        try {
            Thread.sleep(15 * 1000);
            logger.info("Processing complete");
        }
        catch (InterruptedException ie) {
            logger.error("Error in ProcessServiceImpl.process(): {}", ie.getMessage());
        }
    }
}

Step 3: Add an API in the Controller to execute the asynchronous processing

@Autowired
private ProcessService processService;

@RequestMapping(value = "ping/async", method = RequestMethod.GET)
    public ResponseEntity<Map<String, String>> async() {
        processService.process();
        Map<String, String> response = new HashMap<>();
        response.put("message", "Request is under process");
        return new ResponseEntity<>(response, HttpStatus.OK);
    }

I have also written a blog and a working application on GitHub with these steps. Please check: http://softwaredevelopercentral.blogspot.com/2017/07/asynchronous-processing-async-in-spring.html

Solution 3

I had a similar issue and I had the annotations @Async and @EnableAsync in the correct beans and still the method was executing synchronously. After I checked the logs there was a warning saying that I had more than one bean of type ThreadPoolTaskExecutor and none of them called taskExecutor So...

@Bean(name="taskExecutor")
public ThreadPoolTaskExecutor defaultTaskExecutor() {
     ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor();
     //Thread pool configuration
     //...
     return pool;
}

See http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.html for the configuration available for the thread pool.

Solution 4

As code sample for @dave-syer answer:

This works asynchronously:

private void longRunning() {
    try {
        log.info("wait 3 seconds");
        Thread.sleep(3000);
    } catch (InterruptedException e1) {
    }
    log.info("done");               
}

@Async  
@Override
public void doWork() {
    longRunning();
}

But this doesn't:

@Async
private void longRunning() {
    try {
        log.info("wait 3 seconds");
        Thread.sleep(3000);
    } catch (InterruptedException e1) {
    }
    log.info("done");               
}

@Override
public void doWork() {
    longRunning();
}

Solution 5

I used spring-boot main class to define Asynchronous configuration. The @EnableAsync annotation enables Spring’s ability to run @Async methods in a background thread pool. This class also customizes the Executor by defining a new bean. Here, the method is named taskExecutor(), since this is the specific method name searched by Spring.

Spring-Boot-Application.class

@SpringBootApplication
@EnableAsync
public class AsyncApplication {

    @Value("${config.threadpool.corepool.size}")
    private Integer corePoolSize;

    @Value("${config.threadpool.maxpool.size}")
    private Integer maxPoolSize;

    public static void main(String[] args) {
        SpringApplication.run(AsyncApplication.class, args);
    }

    //name of below method should not be changed.
    @Bean
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        //other proeprties to be set here
        executor.setThreadNamePrefix("ASYNC-");
        executor.initialize();
        return executor;
    }
}

In Implementation, use @Async at method level to make the method Asynchronous. Methods need to be public to use @Async. Also, @Async annotated method calling @Async method will not work.

Sample implementation for reference below -

@Async
  public void updateData(String userId) throws ApplicationException {
    logger.info("Updating details for User with {}", userId);
    //your code goes here...
  }

The configuration properties are defined in application.properties file

#Core Pool Size for Async
config.threadpool.corepool.size=100
#Max Pool Size for Async
config.threadpool.maxpool.size=400   

For Rules on how to define the pool, please refer to rules-of-a-threadpool-executor

Share:
89,698

Related videos on Youtube

Web User
Author by

Web User

Updated on August 11, 2020

Comments

  • Web User
    Web User almost 4 years

    My [basic] Spring Boot application accepts a request from the browser, sent via jQuery.get() and is supposed to immediately receive a response - such as "your request has been queued". To accomplish this, I wrote a controller:

    @Controller
    public class DoSomeWorkController {
    
      @Autowired
      private final DoWorkService workService;
    
      @RequestMapping("/doSomeWork")
      @ResponseBody
      public String doSomeWork() {
    
        workService.doWork(); // time consuming operation
        return "Your request has been queued.";
      }
    }
    

    The DoWorkServiceImpl class implements a DoWorkService interface and is really simple. It has a single method to perform a time consuming task. I don't need anything returned from this service call, as an email will be delivered at the end of the work, both for failure or success scenarios. So it would effectively look like:

    @Service
    public class DoWorkServiceImpl implements DoWorkService {
    
      @Async("workExecutor")
      @Override
      public void doWork() {
    
        try {
            Thread.sleep(10 * 1000);
            System.out.println("completed work, sent email");
        }
        catch (InterruptedException ie) {
            System.err.println(ie.getMessage());
        }
      }
    }
    

    I thought this would work, but the browser's Ajax request waited for 10 seconds before returning the response. So the controller mapped method is calling the internal method annotated with @Async synchronously, it would seem. In a traditional Spring application, I typically add this to the XML configuration:

    <task:annotation-driven />
    <task:executor id="workExecutor" pool-size="1" queue-capacity="0" rejection-policy="DISCARD" />
    

    So I thought writing the equivalent of this in the main application class would help:

    @SpringBootApplication
    @EnableAsync
    public class Application {
    
      @Value("${pool.size:1}")
      private int poolSize;;
    
      @Value("${queue.capacity:0}")
      private int queueCapacity;
    
      @Bean(name="workExecutor")
      public TaskExecutor taskExecutor() {
          ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
          taskExecutor.setMaxPoolSize(poolSize);
          taskExecutor.setQueueCapacity(queueCapacity);
          taskExecutor.afterPropertiesSet();
          return taskExecutor;
      }
    
      public static void main(String[] args) {
          SpringApplication.run(Application.class, args);
      }
    }
    

    This did not change the behavior. The Ajax response still arrives after 10 seconds of sending the request. What am I missing?

    The Spring Boot application can be downloaded here. With Maven installed, the project can be run with the simple command:

    mvn clean spring-boot:run
    

    Note The issue was resolved thanks to the answer provided by @Dave Syer below, who pointed out that I was missing @EnableAsync in my application, even though I had the line in the code snippet above.

  • Web User
    Web User over 9 years
    I moved the @Async annotation from the controller method doSomeWork() service method, as the service implementation is a @Service bean. That did not change the behavior.
  • Dave Syer
    Dave Syer over 9 years
    You mean you added @Async to the DoSomeWorkServiceImpl.doSomeWork() method?
  • Web User
    Web User over 9 years
    Yes, moved the annotation from the controller method to the service method.
  • Dave Syer
    Dave Syer over 9 years
    Maybe you need @EnableAsync(proxyTargetClass=true) because your service doesn't have an interface.
  • Dave Syer
    Dave Syer over 9 years
    The "workExecutor" you set up is doing nothing by the way. You need to qualify the @Async for it to use a specific bean (or else add an AsyncConfigurer).
  • Web User
    Web User over 9 years
    I have attached the project ZIP file. It is a really small, dummy application focusing on the problem at hand. I did end up qualifying the @Async as you suggested, after setting up the executor in Application. DoSomeWorkServiceImpl is a @Service bean and an implementation class, by the way.
  • Dave Syer
    Dave Syer over 9 years
    In your sample you don't @EnableAsync anywhere (although it was in the original question). If you add it the app works for me.
  • Web User
    Web User over 9 years
    Somewhere between pasting code snippets and packaging the application, I must have missed adding that in my environment. Sorry for the back-and-forth and thanks for your timely help!
  • ASten
    ASten almost 6 years
    Is creation of the TaskExecutor necessary?
  • Aj Tech Developer
    Aj Tech Developer almost 6 years
    @ASten, Yes, it is necessary.
  • Lord Nick
    Lord Nick almost 6 years
    @AjTechDeveloper are you sure it is necessary to have taskexecutor because i read somewhere, "By default, Spring uses a SimpleAsyncTaskExecutor to actually run these methods asynchronously." which can be overridden.
  • Vivek Sethi
    Vivek Sethi almost 6 years
    The important thing to keep in mind is that, if the async bean is implementing an interface, by default the proxy will be a Java Dynamic Proxy. This means that only external method calls that come in through the proxy will be intercepted – any self-invocation calls will not start the async process – even if the method is annotated with @Async.
  • Aj Tech Developer
    Aj Tech Developer over 5 years
    @Nirmit Srivastava, I have also read about this, but I have not got a chance to try it out.
  • Krzysztof Czelusniak
    Krzysztof Czelusniak over 5 years
    @AjTechDeveloper, so it's not necessary to create TaskExecutor.
  • Victor Grazi
    Victor Grazi over 5 years
    This seems like a reasonable solution. Is there any reason it does not have any upticks?
  • Az.MaYo
    Az.MaYo over 4 years
    My service class doesn't have interface but it's @Async working fine. Could you please explain little bit if service doesn't have interfaces than we need @EnableAsync(proxyTargetClass=true).
  • Dave Syer
    Dave Syer over 4 years
    Recent versions of Spring try to guess the correct value for the proxyTargetClass flag. I guess you benefited from that.
  • Mark Brown
    Mark Brown over 4 years
    I have confirmed @Nirmit's statement that you don't need to create the TaskExecutor. I followed the instructions here: spring.io/guides/gs/async-method I think it is important that your method has a return type of CompletableFuture<T>.
  • Tharsan Sivakumar
    Tharsan Sivakumar about 4 years
    A very straight forward steps. Worked for me like a charm
  • a_a
    a_a over 3 years
    @DaveSyer I have exactly the same situation here and it works fine (works asynchronously). But when I put a debug point in that async method response doesn't come immediately and only comes after I resume from debugging. What could be the reason?
  • namila007
    namila007 almost 3 years
    @a_a I'm having the same issue, why is that?
  • theprogrammer
    theprogrammer over 2 years
    But lets say the service has to return something? how would you handle that in an async way? if you do future.get it will be blocking right? Any suggestion?