Using AsyncContext
in servlets allows you to perform asynchronous request processing. This can improve scalability in situations where a request might involve long-running operations, such as external API calls or database queries, by freeing up server threads while those tasks are performed.
Here’s how you can use AsyncContext
for asynchronous processing in a servlet:
1. Mark the Servlet to Support Asynchronous Processing
You need to mark the servlet explicitly as supporting asynchronous processing using the @WebServlet
annotation or by defining it in web.xml
.
package org.kodejava.servlet;
import jakarta.servlet.AsyncContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@WebServlet(urlPatterns = "/asyncServlet", asyncSupported = true)
// Enable async processing
public class AsyncServlet extends HttpServlet {
// Create a thread pool for processing
private final ExecutorService executor = Executors.newFixedThreadPool(10);
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// Start async processing
AsyncContext asyncContext = request.startAsync();
// Add a timeout if needed
asyncContext.setTimeout(10000); // 10 seconds
// Submit async task to executor
executor.submit(() -> {
try {
// Simulate a long task
Thread.sleep(2000);
// Write response
response.getWriter().write("Asynchronous processing completed!");
} catch (Exception e) {
asyncContext.complete();
e.printStackTrace();
} finally {
// Mark the async context complete
asyncContext.complete();
}
});
}
@Override
public void destroy() {
executor.shutdown(); // Shut down the executor when the servlet is destroyed
}
}
Key Steps in the Code:
- Enable Asynchronous Processing:
Use the@WebServlet
annotation’sasyncSupported = true
or explicitly configure it inweb.xml
. -
Start Asynchronous Context:
Callrequest.startAsync()
to start asynchronous processing. This detaches the request and response from the servlet’s typical request-response lifecycle. -
Set Timeout (Optional):
CallasyncContext.setTimeout()
to define a maximum time for asynchronous processing. If processing exceeds this time, theAsyncListener.onTimeout
method will be triggered, which can be useful for handling timeouts. -
Perform Asynchronous Task:
Use a dedicated thread, thread pool (ExecutorService
), or an external resource to perform long-running tasks. This prevents blocking the server’s thread. -
Complete the Request:
Once processing is completed, callasyncContext.complete()
to end the asynchronous context, signaling the server to finalize the response. -
Handle Exceptions:
Wrap asynchronous operations in atry-catch
block to handle any errors properly and ensureasyncContext.complete()
is always called.
2. Advanced Use: Integrate with AsyncListener
With the AsyncListener
, you can listen to lifecycle events of asynchronous operations, such as completion, timeouts, errors, etc.
package org.kodejava.servlet;
import jakarta.servlet.AsyncContext;
import jakarta.servlet.AsyncEvent;
import jakarta.servlet.AsyncListener;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@WebServlet(urlPatterns = "/asyncWithListener", asyncSupported = true)
public class AsyncServletWithListener extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
AsyncContext asyncContext = request.startAsync();
asyncContext.addListener(new AsyncListener() {
@Override
public void onComplete(AsyncEvent event) {
System.out.println("Async operation completed");
}
@Override
public void onTimeout(AsyncEvent event) {
System.out.println("Async operation timed out");
}
@Override
public void onError(AsyncEvent event) {
System.out.println("Async operation error occurred");
}
@Override
public void onStartAsync(AsyncEvent event) {
System.out.println("Async operation started");
}
});
asyncContext.start(() -> {
try {
// Simulate task
Thread.sleep(2000);
response.getWriter().write("Task processed with listener!");
} catch (Exception e) {
e.printStackTrace();
} finally {
asyncContext.complete();
}
});
}
}
3. Important Notes
- Thread Safety: Request/Response objects are shared among multiple threads in asynchronous processing. Avoid modifying shared data in a non-thread-safe way.
- Clean-up Resources: Always ensure you complete the
AsyncContext
and close any resources used during async processing. - Timeouts: It’s a good practice to handle timeouts using
AsyncListener
to notify the client of any delays.
Using AsyncContext
allows your servlet to better handle high concurrency and long-running tasks, improving the application’s scalability and performance.
Maven dependencies
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.1.0</version>
<scope>provided</scope>
</dependency>