Asynchronous Servlet Implementation

Let’s see steps to implement async servlet and then we will provide async supported servlet for above example.

  1. First of all the servlet where we want to provide async support should have @WebServlet annotation with asyncSupported value as true.
  2. Since the actual work is to be delegated to another thread, we should have a thread pool implementation. We can create thread pool using Executors framework and use servlet context listener to initiate the thread pool.
  3. We need to get instance of AsyncContext through ServletRequest.startAsync() method. AsyncContext provides methods to get the ServletRequest and ServletResponse object references. It also provides method to forward the request to another resource using dispatch() method.
  4. We should have a Runnable implementation where we will do the heavy processing and then use AsyncContext object to either dispatch the request to another resource or write response using ServletResponse object. Once the processing is finished, we should call AsyncContext.complete() method to let container know that async processing is finished.
  5. We can add AsyncListener implementation to the AsyncContext object to implement callback methods – we can use this to provide error response to client incase of error or timeout while async thread processing. We can also do some cleanup activity here.

Initializing Worker Thread Pool in Servlet Context Listener

AppContextListener.java

01 package com.journaldev.servlet.async;
02  
03 import java.util.concurrent.ArrayBlockingQueue;
04 import java.util.concurrent.ThreadPoolExecutor;
05 import java.util.concurrent.TimeUnit;
06  
07 import javax.servlet.ServletContextEvent;
08 import javax.servlet.ServletContextListener;
09 import javax.servlet.annotation.WebListener;
10  
11 @WebListener
12 public class AppContextListener implements ServletContextListener {
13  
14     public void contextInitialized(ServletContextEvent servletContextEvent) {
15  
16         // create the thread pool
17         ThreadPoolExecutor executor = new ThreadPoolExecutor(100, 200, 50000L,
18                 TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(100));
19         servletContextEvent.getServletContext().setAttribute("executor",
20                 executor);
21  
22     }
23  
24     public void contextDestroyed(ServletContextEvent servletContextEvent) {
25         ThreadPoolExecutor executor = (ThreadPoolExecutor) servletContextEvent
26                 .getServletContext().getAttribute("executor");
27         executor.shutdown();
28     }
29  
30 }

The implementation is pretty straight forward, if you are not familiar with Executors framework, please read Thread Pool Executor.

For more details about listeners, please read Servlet Listener Tutorial.

Worker Thread Implementation

AsyncRequestProcessor.java

01 package com.journaldev.servlet.async;
02  
03 import java.io.IOException;
04 import java.io.PrintWriter;
05  
06 import javax.servlet.AsyncContext;
07  
08 public class AsyncRequestProcessor implements Runnable {
09  
10     private AsyncContext asyncContext;
11     private int secs;
12  
13     public AsyncRequestProcessor() {
14     }
15  
16     public AsyncRequestProcessor(AsyncContext asyncCtx, int secs) {
17         this.asyncContext = asyncCtx;
18         this.secs = secs;
19     }
20  
21     @Override
22     public void run() {
23         System.out.println("Async Supported? "
24                 + asyncContext.getRequest().isAsyncSupported());
25         longProcessing(secs);
26         try {
27             PrintWriter out = asyncContext.getResponse().getWriter();
28             out.write("Processing done for " + secs + " milliseconds!!");
29         } catch (IOException e) {
30             e.printStackTrace();
31         }
32         //complete the processing
33         asyncContext.complete();
34     }
35  
36     private void longProcessing(int secs) {
37         // wait for given time before finishing
38         try {
39             Thread.sleep(secs);
40         } catch (InterruptedException e) {
41             e.printStackTrace();
42         }
43     }
44 }

Notice the use of AsyncContext and it’s usage in getting request and response objects and then completing the async processing with complete() method call.

AsyncListener Implementation

AppAsyncListener.java

01 package com.journaldev.servlet.async;
02  
03 import java.io.IOException;
04 import java.io.PrintWriter;
05  
06 import javax.servlet.AsyncEvent;
07 import javax.servlet.AsyncListener;
08 import javax.servlet.ServletResponse;
09 import javax.servlet.annotation.WebListener;
10  
11 @WebListener
12 public class AppAsyncListener implements AsyncListener {
13  
14     @Override
15     public void onComplete(AsyncEvent asyncEvent) throws IOException {
16         System.out.println("AppAsyncListener onComplete");
17         // we can do resource cleanup activity here
18     }
19  
20     @Override
21     public void onError(AsyncEvent asyncEvent) throws IOException {
22         System.out.println("AppAsyncListener onError");
23         //we can return error response to client
24     }
25  
26     @Override
27     public void onStartAsync(AsyncEvent asyncEvent) throws IOException {
28         System.out.println("AppAsyncListener onStartAsync");
29         //we can log the event here
30     }
31  
32     @Override
33     public void onTimeout(AsyncEvent asyncEvent) throws IOException {
34         System.out.println("AppAsyncListener onTimeout");
35         //we can send appropriate response to client
36         ServletResponse response = asyncEvent.getAsyncContext().getResponse();
37         PrintWriter out = response.getWriter();
38         out.write("TimeOut Error in Processing");
39     }
40  
41 }

Notice the implementation of onTimeout() method where we are sending timeout response to client.

Here is the implementation of our async servlet, notice the use of AsyncContext and ThreadPoolExecutor for processing.

AsyncLongRunningServlet.java

01 package com.journaldev.servlet.async;
02  
03 import java.io.IOException;
04 import java.util.concurrent.ThreadPoolExecutor;
05  
06 import javax.servlet.AsyncContext;
07 import javax.servlet.ServletException;
08 import javax.servlet.annotation.WebServlet;
09 import javax.servlet.http.HttpServlet;
10 import javax.servlet.http.HttpServletRequest;
11 import javax.servlet.http.HttpServletResponse;
12  
13 @WebServlet(urlPatterns = "/AsyncLongRunningServlet", asyncSupported = true)
14 public class AsyncLongRunningServlet extends HttpServlet {
15     private static final long serialVersionUID = 1L;
16  
17     protected void doGet(HttpServletRequest request,
18             HttpServletResponse response) throws ServletException, IOException {
19         long startTime = System.currentTimeMillis();
20         System.out.println("AsyncLongRunningServlet Start::Name="
21                 + Thread.currentThread().getName() + "::ID="
22                 + Thread.currentThread().getId());
23  
24         request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);
25  
26         String time = request.getParameter("time");
27         int secs = Integer.valueOf(time);
28         // max 10 seconds
29         if (secs > 10000)
30             secs = 10000;
31  
32         AsyncContext asyncCtx = request.startAsync();
33         asyncCtx.addListener(new AppAsyncListener());
34         asyncCtx.setTimeout(9000);
35  
36         ThreadPoolExecutor executor = (ThreadPoolExecutor) request
37                 .getServletContext().getAttribute("executor");
38  
39         executor.execute(new AsyncRequestProcessor(asyncCtx, secs));
40         long endTime = System.currentTimeMillis();
41         System.out.println("AsyncLongRunningServlet End::Name="
42                 + Thread.currentThread().getName() + "::ID="
43                 + Thread.currentThread().getId() + "::Time Taken="
44                 + (endTime - startTime) + " ms.");
45     }
46  
47 }

Run Async Servlet

Now when we will run above servlet with URL as http://localhost:8080/AsyncServletExample/AsyncLongRunningServlet?time=8000 we get the same response and logs as:

1 AsyncLongRunningServlet Start::Name=http-bio-8080-exec-50::ID=124
2 AsyncLongRunningServlet End::Name=http-bio-8080-exec-50::ID=124::Time Taken=1 ms.
3 Async Supported? true
4 AppAsyncListener onComplete

If we run with time as 9999, timeout occurs and we get response at client side as “TimeOut Error in Processing” and in logs:

01 AsyncLongRunningServlet Start::Name=http-bio-8080-exec-44::ID=117
02 AsyncLongRunningServlet End::Name=http-bio-8080-exec-44::ID=117::Time Taken=1 ms.
03 Async Supported? true
04 AppAsyncListener onTimeout
05 AppAsyncListener onError
06 AppAsyncListener onComplete
07 Exception in thread "pool-5-thread-6" java.lang.IllegalStateException: The request associated with the AsyncContext has already completed processing.
08     at org.apache.catalina.core.AsyncContextImpl.check(AsyncContextImpl.java:439)
09     at org.apache.catalina.core.AsyncContextImpl.getResponse(AsyncContextImpl.java:197)
10     at com.journaldev.servlet.async.AsyncRequestProcessor.run(AsyncRequestProcessor.java:27)
11     at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895)
12     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918)
13     at java.lang.Thread.run(Thread.java:680)

Notice that servlet thread finished execution quickly and all the major processing work is happening in other thread.

Thats all for Async Servlet, I hope you liked it.