How to write an efficient scheduler in Java - Part 1

How to write an efficient scheduler in Java - Part 1


2 min read

While working on my latest project, SolidInbox there are many cases where I need to run one or more jobs, at specific times without needing user input. There are many examples for this:

  • Fetch DMs: I need to fetch user's direct messages regularly so that they can see their Inbox within SolidInbox
  • Location: In SolidInbox you can search via location (e.g. show me all users who live in the UK). The thing is, users can put anything in their Twitter profile and translating them to a specific location needs some work. There needs to be a job in the background to scan through profiles that we have and try to figure out what their location is.
  • Campaign Sender: The messages of a campaign need to be sent in 4-minute intervals. They work even if the user is not logged in. So this needs to be done by a background job.
  • Notifications: There are certain notification emails that need to be sent out at a specific time (e.g. 2 days remaining to trial end).

And many more.

We cannot rely on a web service to run these jobs, as the web service, by definition, is reactive. It only works when there is a request from client side. Without a request, it doesn't do much. There are two main options in JDK to implement a system which schedules background jobs and runs them concurrently.

  1. ScheduledExecutorService: This is a high-level executor, which allows you to schedule jobs to run at specific intervals or with a fixed delay. It has its own ThreadPool inside which can be configured with a size and a ThreadFactory.
  2. DelayQueue: This option is a bit lower level and doesn't include an Executor. It only serves as a queue to hold the jobs that we need to run. Each job has metadata which determines "when" it can run. If you use this option you will also need to add a ThreadPool to it to provide execution infrastructure.

In my case, I decided to go with the second option DelayQueue plus a ThreadPool. I'll explain the reason I picked this option and the way it works, in the next part.