Spring @Async and HttpRequest

This article will help developer to understand the web-request flow and why they should not invoke HttpRequest or RequestContextHolder from inside the Async functions.

Recently, One of the developer reported one issue related to missing some of the basic user information in the request in few of the service class. It was a strange as on production environment we had not received any incident.

After putting Sherlock Hat, I identified that some junior developer added @Async annotation before couple of services without understanding its impact.

Thanks to our automation test suite we identified this issue on our lower environment.

Here, I am writing this article and also creating below mentioned demo application to make junior developer understands :

  • How to use Spring @Async annotation &
  • Why in Async method you would not get HTTP request information

Let’s understand the basic Http Request flow after using Async Annotation:

Web Request Flow

I am sure, most of the developers are following the same pattern, irrespective of the programming language they are using.

In our case, we were getting exception post service 2 layer. And the reason was someone added @Async annotation before some of the methods which were not returning any parameter but were performing some auditing tasks.

In our code, We had one common utility named DomainUtils to retrieve the bannerId from the http request or from RequestContextHolder.

public static String getBannerId() {
return getBannerId(getCurrentRequest());
}

public static String getBannerId(HttpServletRequest request) {
return getRequestHeaderValue("banner-id", request);
}

public static String getRequestHeaderValue(String key, HttpServletRequest request) {
String headerValue = null;
if (request != null && key != null) {
headerValue = request.getHeader(key);
}
return headerValue;
}

public static HttpServletRequest getCurrentRequest() {
HttpServletRequest request = Objects.nonNull(RequestContextHolder
.getRequestAttributes()) ? ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes()).getRequest() : null;
return request;
}

With the below snippet you will find out that DomainUtils.getBannerId() will return bannerId . But after introducing @Async annotation we started getting null

@Async
@Override
public void dummyApiAdaptor() throws Exception {
System.out.println("I am inside DummyAdaptor1Impl::: dummyApiAdaptor ");
System.out.println("Thread ID :::::::::" + Thread.currentThread().getId());
Thread.sleep(50000);
System.out.println("BannerId ::" + DomainUtils.getBannerId());
}

And the reason is

When servlet container creates the request and the request passes through a dispatcher servlet. In the Dispatcher servlet, it identifies the respective Controller by request mapping and calls the desired method of that controller. When the request been served by the controller and controller or its service handover the task to Async thread executor and complete the request. Then servlet container deletes the Http request object. Because of this, In Async method we would not receive any value from httpservlet request.

The Correct way would be :

To pass the information as a value before invoking Async Object. Like, I explained in this below snippet. Firstly retrieve the Http information massage it in some DTO and then use that DTO instead of RequestContextHolder.

// Get the value first , then invoke Async method
dummyAdaptor.dummyApiAdaptor(DomainUtils.getBannerId());
@Async
@Override
public void dummyApiAdaptor(String bannerId) throws Exception {
System.out.println("I am inside DummyAdaptor1Impl::: dummyApiAdaptor with bannerId ::: " + bannerId);
System.out.println("Thread ID :::::::::" + Thread.currentThread().getId());
Thread.sleep(50000);
// Here HttpRequest instance will be destroyed by servlet container and below method will return null
// So while using Async, pass information as parameter don't rely on Http component.
System.out.println("Httprequest BannerId ::" + DomainUtils.getBannerId());
}

Without understanding the consequences, One should not add @Async annotation with the intention to parallel execute the threads. I hope that this article will help junior developers in understanding the concept.

Happy Learning !

Do check this out https://ritresh-girdhar.medium.com For more interesting articles.

Father || Coder || Engineer || Learner || Reader || Writer