Async Servlet
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 Servlets (introduced in Servlet 3.0) allow you to release the request-handling thread back to the pool while the long operation runs in a separate thread. The response is committed only when the async operation completes.
- Improves throughput for I/O-bound operations
- Prevents thread starvation under high load
- Enables server-sent events and long polling
package com.example;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.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 |
@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";
}
}
Ready to Level Up Your Skills?
Explore 500+ free tutorials across 20+ languages and frameworks.