Java Web Apps – Running regular background tasks

Wrapping a ScheduledThreadPoolExecutor with a servlet context listener provides a great, lightweight way to run regular background tasks in a Java web application.

Many are the times we need to run little background tasks to support our web applications. And many are the cheesy hacks we come up with to do it, for sadly a scheduling framework isn’t part of the Enterprise Java web application spec.

Quite often the need arises as a one off, such as clearing down temporary resources, refreshing caches or generating regular activity metrics. Faced with such a simple requirement, integrating frameworks like Quartz seems like a sledgehammer to crack a nut.

Hence the cheesy hacks – such as force-loading a servlet with a never-ending while loop that runs our job then sleeps ’til it’s time to run it again, or just spawning a thread on startup which will plod away in the background, sleeping and waking to do its business. They may leave you feeling like you need a shower after writing them, but they get the job done.

If you want to avoid yet another maven dependency and yet another framework to onboard, but you prefer not to feel too grubby when your day’s coding is done, the java.util.concurrent package may have just what you need. Added in Java 5, this package contains a host of useful classes for multi-threaded applications. Lurking in their midst is ScheduledThreadPoolExecutor – a rather nifty thread pool implementation which allows any number of Runnable tasks to be added for either one-off or repeated future execution. It’s simple to use, simple to manage and there are only a couple of little hurdles to jump to deploy it in a Java web application.

Adding our task scheduler

The first thing we need is to manage a ScheduledThreadPoolExecutor instance for our web application. Since we want to start it when the application starts, and shut it down when the application stops, a ServletContextListener looks like an ideal choice:-

import java.util.concurrent.ScheduledThreadPoolExecutor;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

public class BackgroundTaskManager 
        implements ServletContextListener {

    private static final int MAXIMUM_CONCURRENT = 1;

    private ScheduledThreadPoolExecutor executor = null;

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        executor = new ScheduledThreadPoolExecutor(MAXIMUM_CONCURRENT);
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        executor.shutdown();
    }
}

When contextInitialized() is invoked on startup, we simply create a new ScheduledThreadPoolExecutor instance and store a reference to it for future use. In this example we’re telling it to use a thread pool size of 1, which will ensure all our tasks queue and execute through a single thread. When contextDestroyed() is called on shutdown, we just need to call the shutdown() method on our executor. This will perform a “graceful” stop which allows any in-flight processing to complete. If you want to be sure you web application stops promptly and you’re happy for the background tasks to be terminated mid-flight you can switch that to a shutdownNow() call instead.

To stitch our new context listener into our web application, we just need to add it to the web.xml deployment descriptor:-

<listener>
    <listener-class>
        com.devsumo.webapps.backgroundtasks.BackgroundTaskManager
    </listener-class>
</listener>

Adding our scheduled tasks

We now have a scheduler but it has nothing to schedule. In order to schedule regular tasks to run we need to call the scheduleAtFixedRate() method, which looks like this:-

scheduleAtFixedRate(Runnable command, 
        long initialDelay, long period, TimeUnit unit)

We need to give it a Runnable for each task we want to run and tell it the repeat interval for running it. We could just hard-code our tasks into our BackgroundTaskManager class, though that idea has me reaching for the shower gel again. Configuring them through the deployment descriptor would be nicer.

Unfortunately it isn’t possible to parameterise a listener in the deployment descriptor, but we can add a general context parameter to define our background tasks:-

<context-param>
    <param-name>backgroundTasks</param-name>
    <param-value>
        com.devsumo.webapps.backgroundtasks.SampleTask,60,
        com.devsumo.webapps.backgroundtasks.AnotherSampleTask,40
    </param-value>
</context-param>

We’re keeping this nice and simple here – we’re defining a single backgroundTasks context parameter containing a comma-separated list of the Runnables for our tasks and an interval in seconds for each to run. We can now make our contextInitialized() method pick up this parameter, instantiate each task runner and add it to the pool:-

import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

public class BackgroundTaskManager 
        implements ServletContextListener {

    private static final int MAXIMUM_CONCURRENT = 1;
    private static final String INIT_PARAMETER = "backgroundTasks";

    private ScheduledThreadPoolExecutor executor = null;

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        executor = new ScheduledThreadPoolExecutor(MAXIMUM_CONCURRENT);
        String[] jobDetails = sce.getServletContext().
            getInitParameter(INIT_PARAMETER).split(",(\\s)*");
        for(int i = 0; jobDetails.length > i; i += 2) {
            try {
                Runnable object = (Runnable)Class.forName(
                    jobDetails[i]).newInstance();
                int interval = 
                    Integer.parseInt(jobDetails[i+1]);
                executor.scheduleAtFixedRate(object, 
                    interval, interval, TimeUnit.SECONDS);
            } catch(Exception e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        executor.shutdown();
    }
}

Here we’re accessing our configuration setting via the ServletContext passed in the event parameter to our method. We then use split() to break down the comma separated values before taking each pair in turn, instantiating the Runnable and scheduling it with the appropriate interval. We’re including trailing whitespace in our regular expression for split() which allows us to break our configuration setting into separate lines for each task.

Finally we’ll add a couple of sample tasks to our application:-

public class SampleTask implements Runnable {
    @Override
    public void run() {
        System.out.println("Sample Task running at "
            + new Date());
    }
}

public class AnotherSampleTask implements Runnable {
    @Override
    public void run() {
        System.out.println("Another Sample Task running at "
            + new Date());
    }
}

And then deploy it and see what happens:-

Oct 24, 2013 6:58:08 PM org.apache.catalina.startup.Catalina start
INFO: Server startup in 906 ms
Another Sample Task running at Thu Oct 24 18:58:48 BST 2013
Sample Task running at Thu Oct 24 18:59:08 BST 2013
Another Sample Task running at Thu Oct 24 18:59:28 BST 2013
Sample Task running at Thu Oct 24 19:00:08 BST 2013
Another Sample Task running at Thu Oct 24 19:00:08 BST 2013
Another Sample Task running at Thu Oct 24 19:00:48 BST 2013
Sample Task running at Thu Oct 24 19:01:08 BST 2013
Another Sample Task running at Thu Oct 24 19:01:28 BST 2013

Using a ScheduledThreadPoolExecutor in a Java web application is no more work than the cheesy hacks it replaces and it provides a clean, easy, configurable and lightweight solution for managing a small set of simple, regular background tasks for those situations where a more heavyweight solution feels a bit too much.

Leave a Reply

Your email address will not be published. Required fields are marked *