Spring async method called from another async method

spauny picture spauny · Jul 22, 2014 · Viewed 17.5k times · Source

I'm using Spring 4 and I've noticed an odd behaviour... if I'm calling an async method multiple times from a normal instance method then they are all called in different threads and finish at random times. But if I call multiple times an async method from another async method then they finish in order. I have something like this:

@Async
public void nonAsyncMethod() {
  for (int i = 0; i < 30; i++) {
     asyncMethod();
  }
}

@Async
public void asyncMethod() {
   ... something here
}

I'm using the default async executor. Should I use a different one? However this executor do not reuse any threads and starts another one every time so it should be fine... Can it be just a coincidence? But I've tried like more than 10 times and if I revert back to non-async for the first method then they finish randomly

Answer

geoand picture geoand · Jul 23, 2014

What you are describing is a classic pitfall of Spring AOP.

In short, for Spring to be able to provide the async behavior it needs to create a proxy for your class at runtime. The proxy then does whatever it needs to do before and/or after calling your code. But in your case, the proxy mechanism is not being applied for the second method.

When a bean of your class is injected via Spring into some other component, Spring really injects the proxy instead. Therefor the relevant method of the proxy is called. However, when you are calling a method from inside the class, the limitations of Spring AOP mean the proxy never comes into play, but instead the regular method is called - with no extra features.

That is why asyncMethod is always executing on the same thread as the other method in the same class that called it.

Check out this excellent blog post as well as this part of Spring documentation.

There are some ways around the problem (check out this) that don't require you to refactor your code, but if you want async to work on both methods no matter what, the simplest thing to do is refactor the second method into another class.