50

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
  • 7,438
  • 14
  • 64
  • 92
  • Possible duplicate of [@Async not working for me](https://stackoverflow.com/questions/4060718/async-not-working-for-me) – xerx593 Feb 22 '19 at 16:18

6 Answers6

88

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.

Dave Syer
  • 56,583
  • 10
  • 155
  • 143
  • 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. – Web User Mar 26 '15 at 19:13
  • You mean you added `@Async` to the `DoSomeWorkServiceImpl.doSomeWork()` method? – Dave Syer Mar 27 '15 at 10:01
  • Yes, moved the annotation from the controller method to the service method. – Web User Mar 27 '15 at 10:41
  • 1
    Maybe you need `@EnableAsync(proxyTargetClass=true)` because your service doesn't have an interface. – Dave Syer Mar 27 '15 at 12:03
  • 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`). – Dave Syer Mar 27 '15 at 12:06
  • I have attached the [project ZIP file](https://drive.google.com/file/d/0B_ws0yWyg96JRUNxN0ROcF9Ja1U/view?usp=sharing). 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. – Web User Mar 27 '15 at 13:43
  • 4
    In your sample you don't `@EnableAsync` anywhere (although it was in the original question). If you add it the app works for me. – Dave Syer Mar 27 '15 at 17:50
  • 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! – Web User Mar 27 '15 at 17:58
  • 1
    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. – Vivek Sethi Sep 12 '18 at 06:28
  • 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). – Az.MaYo Nov 27 '19 at 05:47
  • Recent versions of Spring try to guess the correct value for the `proxyTargetClass` flag. I guess you benefited from that. – Dave Syer Nov 27 '19 at 15:24
  • @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? – a_a Sep 30 '20 at 15:00
  • @a_a I'm having the same issue, why is that? – namila007 Aug 12 '21 at 12:47
30

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

5

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.

Carlos Andres
  • 417
  • 4
  • 13
3

Follow the three steps :

1 Step : Use @EnableAsync with @configuration or @SpringBootApplication

@EnableAsync public class Application {

2 Step :

/**
 * THIS FOR ASYNCRONOUS PROCESS/METHOD
 * @return
 */
@Bean
public Executor asyncExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(5);
    executor.setMaxPoolSize(5);
    executor.setQueueCapacity(500);
    executor.setThreadNamePrefix("Asynchronous Process-");
    executor.initialize();
    return executor;
}

3 Step : Put @Async over the intended method

T

Victor Grazi
  • 15,563
  • 14
  • 61
  • 94
3

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();
}
marioosh
  • 27,328
  • 49
  • 143
  • 192
2

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

Sourabh
  • 413
  • 5
  • 17