The Hidden Pitfall of Passing HttpServletRequest to Async Threads in Spring Boot

Passing an HttpServletRequest into an asynchronous thread in Spring Boot can cause mysterious parameter loss and request‑recycling bugs, because Tomcat reuses request objects; the proper solution is to employ the servlet's startAsync and AsyncContext.complete methods for safe async processing.

Java Backend Technology
Java Backend Technology
Java Backend Technology
The Hidden Pitfall of Passing HttpServletRequest to Async Threads in Spring Boot

Problem Overview

When a HttpServletRequest is handed over to a thread pool in a Spring Boot application, developers may observe that request parameters disappear or that subsequent requests throw "missing required parameter" errors. The root cause is Tomcat's reuse of Request objects after the original servlet method returns.

Reproducing the Issue

The following minimal controller demonstrates the problem:

@GetMapping("/getTest")
public String getTest(HttpServletRequest request) {
    String age = request.getParameter("age");
    System.out.println("age=" + age);
    new Thread(() -> {
        try { Thread.sleep(200); } catch (InterruptedException e) { throw new RuntimeException(e); }
        String age1 = request.getParameter("age");
        System.out.println("age1=" + age1);
    }).start();
    return "success";
}

Calling http://127.0.0.1:8080/getTest?age=18 prints the age in the main thread, but the asynchronous thread often prints null on the second request.

Debugging Findings

By setting the logging level to debug and tracing the call stack, the following observations were made:

The first request triggers Parameters.processParameters, which parses the query string and stores values in a map.

After the servlet method finishes, Tomcat calls Request.recycle, clearing the map and resetting the flag didQueryParameters to false.

If an asynchronous thread accesses request.getParameter after the recycle, the flag may already be true, causing the method to skip parsing and return null.

Tomcat Parameters processing
Tomcat Parameters processing

Tomcat Request Lifecycle

The Servlet specification states that a request object is valid only within the servlet's service method unless asynchronous processing is started with request.startAsync(). Containers typically recycle request objects to avoid allocation overhead, which explains the observed behavior.

Key excerpts from the spec:

Each request object is valid only within the scope of a servlet’s service method, unless the asynchronous processing is enabled for the component and the startAsync method is invoked on the request object.
In the case where asynchronous processing occurs, the request object remains valid until complete is invoked on the AsyncContext.
Containers commonly recycle request objects in order to avoid the performance overhead of request object creation.

Correct Asynchronous Handling

To safely use a request in another thread, start asynchronous processing explicitly and complete it when the work is done:

@GetMapping("/getTest")
public void getTest(HttpServletRequest request, HttpServletResponse response) throws IOException {
    AsyncContext async = request.startAsync();
    async.start(() -> {
        try {
            String age = request.getParameter("age");
            // business logic here
            response.getWriter().write("age=" + age);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            async.complete();
        }
    });
}

Using startAsync keeps the request alive beyond the servlet method, prevents premature recycling, and guarantees that AsyncContext.complete() will finally release the resources.

AsyncContext usage
AsyncContext usage

In summary, never pass a HttpServletRequest to a plain thread pool; always use the servlet asynchronous API ( startAsync / AsyncContext.complete) to avoid the hidden request‑recycling bug.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

DebuggingSpring BootServletTomcatHttpServletRequestAsync
Java Backend Technology
Written by

Java Backend Technology

Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.