Tutorials Logic, IN info@tutorialslogic.com

Async Servlet AsyncContext, Non blocking I/O

Asynchronous Request Lifecycle

Servlet asynchronous processing releases the container request thread while application work continues, then completes or dispatches the AsyncContext later. It helps when waiting on nonblocking or separately managed work; it does not make a blocking database call faster or remove the need for capacity limits.

Why Async Servlets?

By default, each servlet request occupies a server thread for its entire duration. For long-running operations (database queries, external API calls, file processing), this blocks the thread and limits scalability.

Async processing requires Servlet 3.0 or later. It releases the request-handling thread while another execution path completes the work; the application must manage timeout, dispatch, completion, errors, and response commitment explicitly.

  • Improves throughput for I/O-bound operations
  • Prevents thread starvation under high load
  • Enables server-sent events and long polling

Async Servlet with AsyncContext

Async Servlet with AsyncContext
package com.example;

import jakarta.servlet.*;
import jakarta.servlet.http.*;
import jakarta.servlet.annotation.WebServlet;
import java.io.*;
import java.util.concurrent.*;

// asyncSupported=true is REQUIRED to enable async processing
@WebServlet(urlPatterns = "/async-task", asyncSupported = true)
public class AsyncServlet extends HttpServlet {

    // Use a thread pool for async tasks
    private ExecutorService executor;

    @Override
    public void init() {
        executor = Executors.newFixedThreadPool(10);
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        response.setContentType("text/html;charset=UTF-8");

        // 1. Start async processing - releases the request thread
        AsyncContext asyncContext = request.startAsync();

        // 2. Set a timeout (milliseconds). Default is 30000ms.
        asyncContext.setTimeout(60000);

        // 3. Add a listener to handle completion/timeout/error
        asyncContext.addListener(new AsyncListener() {
            public void onComplete(AsyncEvent event) throws IOException {
                System.out.println("Async task completed.");
            }
            public void onTimeout(AsyncEvent event) throws IOException {
                HttpServletResponse resp = (HttpServletResponse) event.getSuppliedResponse();
                resp.sendError(HttpServletResponse.SC_GATEWAY_TIMEOUT, "Request timed out.");
                event.getAsyncContext().complete();
            }
            public void onError(AsyncEvent event) throws IOException {
                System.err.println("Async error: " + event.getThrowable());
                event.getAsyncContext().complete();
            }
            public void onStartAsync(AsyncEvent event) {}
        });

        // 4. Submit the long-running task to the thread pool
        executor.submit(() -> {
            try {
                // Simulate a long-running operation (e.g., DB query, API call)
                Thread.sleep(3000);

                // Write the response from the async thread
                PrintWriter out = asyncContext.getResponse().getWriter();
                out.println("<h2>Async Task Completed!</h2>");
                out.println("<p>Processed in background thread: "
                          + Thread.currentThread().getName() + "</p>");

            } catch (Exception e) {
                try {
                    asyncContext.getResponse().getWriter()
                                .println("Error: " + e.getMessage());
                } catch (IOException ignored) {}
            } finally {
                // 5. MUST call complete() to commit the response
                asyncContext.complete();
            }
        });

        // The request thread is now free to handle other requests
        System.out.println("Request thread released: " + Thread.currentThread().getName());
    }

    @Override
    public void destroy() {
        executor.shutdown();
    }
}

dispatch() vs complete()

After the async operation finishes, you have two options to end the async cycle:

Method Description Use Case
asyncContext.complete() Commits the response directly from the async thread When you write the response yourself in the async thread
asyncContext.dispatch(path) Forwards the request to a servlet or JSP for rendering When you want a JSP to render the result
asyncContext.dispatch() Dispatches back to the original request URI Re-process the original URL after async work

Async with dispatch() to JSP

Async with dispatch() to JSP
@WebServlet(urlPatterns = "/report", asyncSupported = true)
public class AsyncDispatchServlet extends HttpServlet {

    private ExecutorService executor = Executors.newCachedThreadPool();

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        AsyncContext asyncContext = request.startAsync(request, response);
        asyncContext.setTimeout(30000);

        executor.submit(() -> {
            try {
                // Simulate fetching report data
                Thread.sleep(2000);
                String reportData = generateReport();

                // Store result as request attribute
                asyncContext.getRequest().setAttribute("reportData", reportData);

                // Dispatch to JSP for rendering (runs in a new request thread)
                asyncContext.dispatch("/WEB-INF/views/report.jsp");

            } catch (Exception e) {
                asyncContext.dispatch("/WEB-INF/views/error.jsp");
            }
        });
    }

    private String generateReport() {
        return "Sales Report: $50,000 total revenue";
    }
}

Completion Rules

Call startAsync only when the servlet and every participating filter support async. Set a bounded timeout and add an AsyncListener for completion, timeout, and error cleanup. Exactly one path should complete or dispatch the context, including exceptional and timeout paths.

Do not use the original request thread’s ThreadLocal security or logging context blindly in background work. Propagate only required context through supported mechanisms. Use a managed executor where the platform provides one, apply backpressure, and test client disconnects as well as normal completion.

Before you move on

Async Servlet AsyncContext, Non blocking I/O Mastery Check

4 checks
  • By default, each servlet request occupies a server thread for its entire duration.
  • For long-running operations (database queries, external API calls, file processing), this blocks the thread and limits scalability.
  • Servlet 3.0+ async processing releases the request thread but still requires explicit timeout, completion, and error handling.
  • The response is committed only when the async operation completes.
Next Step
Next Practice

Finish the concept here, then reinforce it with hands-on coding, interview prep, or a tool that matches the topic.

Browse Free Tutorials

Explore 500+ free tutorials across 20+ languages and frameworks.