8

@Async method in @Service annotated class in standalone Spring Boot application doesn't run asynchronously. What am I doing wrong?

When I run the same method directly from main class (@SpringBootApplication annotated), it works. Example:

Main class

@SpringBootApplication
@EnableAsync
public class Application implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        // here when I call downloadAnSave() it runs asynchronously...
        // but when I call downloadAnSave() via downloadAllImages() it does not run asynchronously...
    }

}

and my service class (and here asynchronous behavior doesn't work):

@EnableAsync
@Service
public class ImageProcessorService implements IIMageProcessorService {

    public void downloadAllImages(Run lastRun) {
        // this method calls downloadAnSave() in loop and should run asynchronously....
    }

    @Async
    @Override
    public boolean downloadAnSave(String productId, String imageUrl) {
        //
    }

}
Jason Law
  • 965
  • 1
  • 9
  • 21
jnemecz
  • 3,171
  • 8
  • 41
  • 77
  • 2
    Possible duplicate of [Spring Boot @Async method in controller is executing synchronously](http://stackoverflow.com/questions/29284008/spring-boot-async-method-in-controller-is-executing-synchronously) – g00glen00b Oct 14 '16 at 12:25
  • The question itself does not seem to be an exact duplicate, but the same answer (and comments) apply here. – g00glen00b Oct 14 '16 at 12:27

2 Answers2

25

Calling async method from within the same class would trigger the original method and not the intercepted one. You need to create another service with the async method, and call it from your service.

Spring creates a proxy for each service and component you create using the common annotations. Only those proxies contain the wanted behavior defined by the method annotations such as the Async. So, calling those method not via the proxy but by the original naked class would not trigger those behaviors.

aviad
  • 1,553
  • 12
  • 15
  • 1
    Thank you, I'll try it. For clarification and my information, please can you explain what you mean with "would trigger the original method and not the intercepted one"? Thanks. – jnemecz Oct 14 '16 at 12:10
  • You can also self-inject and it would work, but I don't think it's advised, as it usually breaks the Single Responsibility Principle – Jan Siekierski Jul 12 '19 at 08:12
  • Can you explain why self-inject breaks "Single Responsibility Principle"? – Jason Law Sep 06 '19 at 10:28
  • 1
    Thanks a lot. working perfectly in another service – apr Dec 20 '21 at 12:26
3

Workaround is:

@EnableAsync
@Service("ip-service")
public class ImageProcessorService implements IIMageProcessorService {
    
    @Autowired
    @Qualifier("ip-service")
    ImageProcessorService ipService;

    public void downloadAllImages(Run lastRun) {
        // this method calls downloadAnSave() in loop and should run asynchronously....
        ipService.downloadAnSave(productId, imageUrl);
    }

    @Async
    @Override
    public boolean downloadAnSave(String productId, String imageUrl) {
        //
    }
}

With such approach you call method of proxy, not the class instance. The same approach can be used with other tools working with proxies, e.g. @Transactional etc.

Torino
  • 445
  • 5
  • 12
  • It doesn't work in latest Springboot version, and says: "Relying upon circular references is discouraged and they are prohibited by default." – AlexS Feb 28 '22 at 01:38
  • 1
    You can avoid this error by adding @ Lazy to the @ Autowired property. – Markus Mangei Jul 20 '22 at 14:35