Demystifying the Magic of Spring: @Async

  • 26 October 2018
  • 5 minutes to read
  • Share on:

Spring Framework is widely known for its magic. It’s a holy grail for junior Java developers, pride for some senior and shame for the others. Spring combines old hacks that once made Java the privileged language of enterprise software development. However, most of these hacks are no longer needed and only lead to unmaintainable code.
One of these hacks is the @Async annotation that makes methods asynchronous under the hood.
Let’s discuss the problems with @Async and explore the modern alternative way to do asynchronous computations in Java.

Using @Async

Configuring @Async is easy. Add @EnableAsync to your Spring configuration and enjoy its power.

Let’s start with the simplest case - running a void method asynchronously.

As we expected, this method will run asynchronously. However, there is a nuance. @Async annotation has one parameter - the name of the executor that runs the annotated method. We haven’t specified any executor, therefore the SimpleAsyncTaskExecutor will be used by default. Guess what it does. It fires up a new Thread for each method execution! It might be a reasonable default for a hello-world app, but it’s absolutely inappropriate for production. There is a workaround to implement AsyncConfigurer interface in the async configuration that forces you to specify the default executor, but not everyone is aware of it (maybe because it’s missing in the official tutorial).

Let’s define an executor bean in our Spring configuration and use its name as the @Async annotation parameter.

Now we’re in better control of resources allocated to async computations in our app.

What if somebody accidentally changes the name of the executor bean or makes a mistake in the @Async argument value?
We’ll figure it out neither at compile time nor at the application startup. Instead, we’ll get a NoSuchBeanDefinitionException just when we call the async method for the first time. Hopefully, we have integration tests that will catch this problem. But in practice, the async effect is often disabled in favor of easier and more predictable testing, or Spring configuration for testing differs too much from the production one.

What if we annotate private method with @Async?

By default, private methods will be executed synchronously without any warning due to the limitations of Spring AOP. The only way to overcome this limitation is by turning on compile-time weaving.

What if the async method throws an uncaught exception?

Client object won’t see this exception because it will be thrown in a different thread. But Spring cares about us providing an AsyncUncaughtExceptionHandler hack to handle these unforunate exceptions.

I would argue that this error handling logic is too far away from the accident and is not as efficient as the error handling on the Client side could be.

What if somebody decides to return a raw value from the @Async method?

Interestingly, the method will run asynchronously immediately returning null to the caller. NullPointerException is just a matter of time.

We can solve it only by wrapping the return value in the Future. However, this approach introduces the problem of dealing with an outdated (synchronous and non-composable) Future.get(_) API on the client side.

Unfortunately, this is the best we can get with @Async.
Combining it with the poor testing support that requires Spring context initialization and configuration juggling, we get maintenance problems resulting in unpredictable behavior in production. Hopefully, there is a solution.

Modern Approach to Async Programming

@Async annotation was introduced in Spring 3.0. It was December 2009, Java 6 era with a weak Future abstraction.
5 years later Java 8 released with a game-changing CompletableFuture API.
CompletableFuture abstracts a composable asynchronous computation. Let’s use it in our example.

You see, there is no magic.
AsyncObject is a regular Java object with a mandatory explicit dependency on the executor defined in the constructor. Each method has predictable behavior, there are multiple ways to compose them, and exception handling is intuitive.

But what about testing?

Yes, it’s a simple and fast unit test we all missed so much after using Spring for a long time.

Finally, if you’re migrating to a reactive web framework such as Spring WebFlux with Reactor, CompletableFuture is a step in the right direction. You can construct Mono<T> from CompletableFuture<T> wrapping it into Supplier<T> to achieve inherent laziness of reactive streams that is missing in CompletableFuture.

Although Project Reactor is relatively new, a bunch of Java libraries already expose fully asynchronous CompletableFuture API. You can easily embed them into your reactive app without problems.

After all, it’s no longer a question of whether to use @Async or not.
Learn more about CompletableFuture and enjoy asynchronous programming in Java.

Resources: