Servlets
Web Application
Background Task
Java
Multithreading

How to run a background task in a servlet based web application?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Background work in a servlet-based web application is a common need for tasks such as sending emails, processing uploads, or running scheduled maintenance. The challenge is doing it in a way that fits the servlet container’s lifecycle rather than fighting against it.

The safest rule is simple: do not create unmanaged ad hoc threads inside request handlers unless you fully understand the container consequences. Prefer container-managed executors or a clearly owned scheduler, and keep request handling separate from long-running background work.

Why Raw Threads Are a Bad Default

A naive approach is to start a thread directly from a servlet:

java
new Thread(() -> doWork()).start();

This looks easy, but it has real problems:

  • the container does not manage the thread lifecycle
  • redeploy or shutdown can leave work in an undefined state
  • you have no structured error handling or resource control
  • thread creation inside request code can scale badly

In other words, it works as a quick demo but is usually a poor production pattern.

Use a Managed Executor When Available

In a Jakarta EE or Java EE style environment, a managed executor is the preferred option because the container owns the threading resources.

java
1import jakarta.annotation.Resource;
2import jakarta.enterprise.context.ApplicationScoped;
3import jakarta.enterprise.concurrent.ManagedExecutorService;
4
5@ApplicationScoped
6public class BackgroundJobs {
7
8    @Resource
9    ManagedExecutorService executor;
10
11    public void submit(Runnable task) {
12        executor.submit(task);
13    }
14}

Then a servlet can submit work without manually creating threads:

java
1import jakarta.inject.Inject;
2import jakarta.servlet.annotation.WebServlet;
3import jakarta.servlet.http.HttpServlet;
4import jakarta.servlet.http.HttpServletRequest;
5import jakarta.servlet.http.HttpServletResponse;
6import java.io.IOException;
7
8@WebServlet("/start-job")
9public class JobServlet extends HttpServlet {
10
11    @Inject
12    BackgroundJobs jobs;
13
14    @Override
15    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
16        jobs.submit(() -> System.out.println("Background task running"));
17        resp.getWriter().println("Job submitted");
18    }
19}

This is much better aligned with the application server model.

If You Control the Whole App, Use a Managed Scheduler Pattern

In simpler servlet deployments where a managed executor is not available, a controlled ScheduledExecutorService created at application startup is a more disciplined fallback than random per-request threads.

java
1import jakarta.servlet.ServletContextEvent;
2import jakarta.servlet.ServletContextListener;
3import jakarta.servlet.annotation.WebListener;
4import java.util.concurrent.Executors;
5import java.util.concurrent.ScheduledExecutorService;
6import java.util.concurrent.TimeUnit;
7
8@WebListener
9public class AppLifecycle implements ServletContextListener {
10
11    private ScheduledExecutorService scheduler;
12
13    @Override
14    public void contextInitialized(ServletContextEvent sce) {
15        scheduler = Executors.newScheduledThreadPool(2);
16        sce.getServletContext().setAttribute("scheduler", scheduler);
17    }
18
19    @Override
20    public void contextDestroyed(ServletContextEvent sce) {
21        scheduler.shutdown();
22    }
23}

A servlet can retrieve the scheduler from the context and submit jobs to it. This is still app-managed, but at least the lifecycle is centralized and shutdown is handled.

Keep the Request Fast

A servlet request should usually do just enough to validate input, enqueue or submit the work, and return a response. Long-running business logic should stay out of the request thread.

That gives you better responsiveness and avoids tying request timeouts to background-processing time.

A good pattern is:

  1. validate request data
  2. submit background work
  3. return a job ID or acknowledgment
  4. expose a separate endpoint for status if needed

That model scales much better than blocking the request until the task finishes.

Consider External Job Systems for Important Work

If the background work is critical, long-running, or needs retries across restarts, a servlet-local executor may still not be enough. In that case, use a queue, scheduler, or dedicated worker system outside the servlet container.

Examples include:

  • message queues
  • dedicated worker services
  • Quartz-style schedulers
  • batch processing systems

The servlet app then becomes the place that accepts requests and submits jobs, not the place that owns every long-lived background execution detail.

Common Pitfalls

One common mistake is starting raw threads inside servlets and never shutting them down on redeploy. That can leak resources and create duplicate workers after application reloads.

Another issue is doing heavy work on the request thread and calling it “background” just because it was triggered by a request. If the HTTP thread is still blocked, it is not really background work.

It is also easy to ignore error handling. Background tasks still need logging, retry policy, and a clear ownership model.

Finally, do not assume all servlet containers manage custom threads safely. Container-managed concurrency and unmanaged Java threads are not equivalent.

Summary

  • Avoid creating unmanaged ad hoc threads directly inside servlet request handlers.
  • Prefer a container-managed executor when the platform provides one.
  • If you must manage your own scheduler, create and shut it down through application lifecycle hooks.
  • Keep HTTP requests short by submitting work rather than performing long background tasks inline.
  • For important or durable jobs, consider an external queue or worker system instead of keeping everything inside the servlet container.

Course illustration
Course illustration

All Rights Reserved.