Common Issues

1. java.lang.IllegalStateException - No thread-bound request found

Case 1

We have an HTTP endpoint that processes a payment and then asynchronously sends an SMS using a method annotated with @Async. Inside the async method, some values are accessed from the HttpServletRequest or request-scoped beans.

Error Message:

java.lang.IllegalStateException: No thread-bound request found: 
Are you referring to request attributes outside of an actual web request, 
or processing a request outside of the originally receiving thread? 
If you are actually operating within a web request and still receive this message, 
your code is probably running outside of DispatcherServlet: 
In this case, use RequestContextListener or RequestContextFilter to expose the current request.

Root Cause:

Spring manages the HTTP request lifecycle using thread-bound context. That means request-related objects (like HttpServletRequest, RequestAttributes, or beans scoped as request) are available only within the thread that handled the request.

When we use @Async, the annotated method executes in a separate thread, which does not have access to the original thread’s request context. So any attempt to read request attributes (like headers, parameters, or session data) from the async method will result in this exception.

Solution:

Option 1: Pass Required Data Explicitly

The recommended and safest approach is to extract any necessary values from the request before calling the async method, and pass them as method arguments.

@PostMapping("/pay")
public ResponseEntity<Void> processPayment(HttpServletRequest request) {
    String userId = request.getHeader("X-USER-ID");
    paymentService.process(); // sync logic
    notificationService.sendSmsAsync(userId); // pass needed data explicitly
    return ResponseEntity.ok().build();
}

@Async
public void sendSmsAsync(String userId) {
    // use userId directly, no need for request context
}

Option 2: Register a RequestContextListener

If we absolutely need access to the request context in the async thread, register a RequestContextListener in our configuration:

@Bean
public RequestContextListener requestContextListener() {
    return new RequestContextListener();
}

Note: Even with this, it's not always reliable to depend on request context in async threads, especially under heavy load or across thread pools.

Option 3: Use a RequestContextFilter (Alternative to Listener)

If our application uses filters and we want to ensure consistent behavior across threads, we can use RequestContextFilter:

@Bean
public FilterRegistrationBean<RequestContextFilter> requestContextFilter() {
    FilterRegistrationBean<RequestContextFilter> registration = new FilterRegistrationBean<>();
    registration.setFilter(new RequestContextFilter());
    return registration;
}

Case 2

We are using @Asyn method which runs after main thread is over. This method calls another api and uses private final HttpServletRequest request but request is no more available as main thread is exited.

The issue arises because HttpServletRequest is inherently tied to the main thread's lifecycle in Java web applications. Once the main thread exits, the HttpServletRequest object is no longer valid or accessible.

When using @Async methods in Spring Boot, the code runs in a different thread than the one handling the HTTP request. To solve this issue, you need to extract the necessary information from HttpServletRequest before the main thread exits and pass it explicitly to the @Async method.

1. Clone the Request Data

Manually copy the relevant information from the HttpServletRequest before invoking the @Async method. We can store the information in a Map or a custom object.

Example:

Main Thread

@PostMapping("/process")
public ResponseEntity<String> processRequest(HttpServletRequest request) {
    // Clone the request's data into a Map
    Map<String, String> requestData = new HashMap<>();
    Enumeration<String> headerNames = request.getHeaderNames();
    while (headerNames.hasMoreElements()) {
        String headerName = headerNames.nextElement();
        requestData.put(headerName, request.getHeader(headerName));
    }

    // Pass the cloned data to the async service
    asyncService.asyncMethod(requestData);

    return ResponseEntity.ok("Request processing started!");
}

Async Service

@Service
public class AsyncService {

    @Async
    public void asyncMethod(Map<String, String> requestData) {
        // Use the cloned request data
        String headerValue = requestData.get("X-Custom-Header");
        System.out.println("Async thread processing with header: " + headerValue);

        // Call another API, using the cloned data
    }
}

2. Use a ThreadLocal for Propagation

Use a ThreadLocal to propagate the HttpServletRequest to the async thread. We can copy the data into a ThreadLocal before invoking the @Async method.

Utility Class

public class RequestContext {
    private static final ThreadLocal<HttpServletRequest> requestHolder = new ThreadLocal<>();

    public static void setRequest(HttpServletRequest request) {
        requestHolder.set(request);
    }

    public static HttpServletRequest getRequest() {
        return requestHolder.get();
    }

    public static void clear() {
        requestHolder.remove();
    }
}

Main Thread

@PostMapping("/process")
public ResponseEntity<String> processRequest(HttpServletRequest request) {
    // Set the request in ThreadLocal
    RequestContext.setRequest(request);

    asyncService.asyncMethod();

    return ResponseEntity.ok("Request processing started!");
}

Async Service

@Service
public class AsyncService {

    @Async
    public void asyncMethod() {
        try {
            // Retrieve the request from ThreadLocal
            HttpServletRequest request = RequestContext.getRequest();
            if (request != null) {
                String headerValue = request.getHeader("X-Custom-Header");
                System.out.println("Async thread processing with header: " + headerValue);
            }
        } finally {
            // Clean up the ThreadLocal to prevent memory leaks
            RequestContext.clear();
        }
    }
}

Caution: Using ThreadLocal with @Async should be done carefully, as Spring may use thread pooling for async tasks. Always clear the ThreadLocal to avoid memory leaks or incorrect data propagation.

3. Use RequestContextHolder

Spring provides RequestContextHolder for accessing the current HttpServletRequest. However, we must configure Spring to allow request context propagation to async threads.

Configuration

Enable request context propagation in Spring Boot by setting the following property in application.properties:

spring.web.async.request-timeout=30000

Access Request in Async Method

@Service
public class AsyncService {

    @Async
    public void asyncMethod() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String headerValue = request.getHeader("X-Custom-Header");
        System.out.println("Async thread processing with header: " + headerValue);

        // Call another API using the request data
    }
}

2. ScopeNotActiveException in Async/Executor Threads

The error we're encountering arises because the @RequestScope beans in Spring are tied to the HTTP request lifecycle. When using ExecutorService to execute tasks asynchronously, the task runs in a different thread from the original HTTP request thread, and the RequestScope is not active in the new thread.

Why This Happens

  1. Request Scope Lifecycle:

    • Beans annotated with @RequestScope are bound to the lifecycle of the HTTP request.

    • When the original HTTP thread finishes processing the request, the request scope is destroyed.

  2. Asynchronous Threads:

    • When we submit tasks to ExecutorService, these tasks are executed in separate threads. These threads are independent of the HTTP request and do not have access to the RequestScope.

Solutions

1. Use Scoped Proxies

Spring allows us to use a scoped proxy for @RequestScope beans. This creates a proxy object that resolves the actual bean at runtime, ensuring the correct RequestScope context is used.

Example:

@Bean
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public MyRequestScopedBean requestScopedBean() {
    return new MyRequestScopedBean();
}

If our bean is already annotated with @RequestScope:

@RequestScope
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyRequestScopedBean {
    // Implementation
}

2. Pass Contextual Information

Instead of relying on @RequestScope beans in an asynchronous task, pass the required request-scoped data as method parameters. Extract necessary data in the controller layer and pass it to the service or task.

Example:

public void asyncTask(String requestScopedData) {
    executorService.submit(() -> {
        // Use the passed data
        System.out.println(requestScopedData);
    });
}

3. Use @Async with Context Propagation

The @Async annotation in Spring can manage scoped beans if properly configured with context propagation. We can use libraries like Spring Cloud Sleuth or TaskDecorator to propagate the request context.

  • Example with TaskDecorator:

    @Bean
    public TaskDecorator taskDecorator() {
        return runnable -> {
            RequestAttributes context = RequestContextHolder.currentRequestAttributes();
            return () -> {
                try {
                    RequestContextHolder.setRequestAttributes(context);
                    runnable.run();
                } finally {
                    RequestContextHolder.resetRequestAttributes();
                }
            };
        };
    }
    
    @Bean
    public Executor taskExecutor(TaskDecorator taskDecorator) {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(50);
        executor.setTaskDecorator(taskDecorator);
        executor.initialize();
        return executor;
    }

    This setup propagates the request context into asynchronous threads.

4. Avoid Using @RequestScope for Long-Lived Tasks

If our task requires data for processing that might outlive the request, consider fetching or caching the required data before starting the asynchronous task.

Last updated