53

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.

1

6 Answers 6

93

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.

Sign up to request clarification or add additional context in comments.

13 Comments

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.
You mean you added @Async to the DoSomeWorkServiceImpl.doSomeWork() method?
Maybe you need @EnableAsync(proxyTargetClass=true) because your service doesn't have an interface.
In your sample you don't @EnableAsync anywhere (although it was in the original question). If you add it the app works for me.
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.
|
31

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

8 Comments

Is creation of the TaskExecutor necessary?
@ASten, Yes, it is necessary.
@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.
@AjTechDeveloper, so it's not necessary to create TaskExecutor.
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>.
|
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.

Comments

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

2 Comments

This seems like a reasonable solution. Is there any reason it does not have any upticks?
@Async should be used on public method
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();
}

Comments

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.