How do I use AsyncContext for asynchronous processing in servlets?

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:

  1. Enable Asynchronous Processing:
    Use the @WebServlet annotation’s asyncSupported = true or explicitly configure it in web.xml.

  2. Start Asynchronous Context:
    Call request.startAsync() to start asynchronous processing. This detaches the request and response from the servlet’s typical request-response lifecycle.

  3. Set Timeout (Optional):
    Call asyncContext.setTimeout() to define a maximum time for asynchronous processing. If processing exceeds this time, the AsyncListener.onTimeout method will be triggered, which can be useful for handling timeouts.

  4. 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.

  5. Complete the Request:
    Once processing is completed, call asyncContext.complete() to end the asynchronous context, signaling the server to finalize the response.

  6. Handle Exceptions:
    Wrap asynchronous operations in a try-catch block to handle any errors properly and ensure asyncContext.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>

Maven Central

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.