Jesque is an implementation of Resque in Java. It is fully-interoperable with the Ruby and Node.js (Coffee-Resque) implementations.
Jesque is a Maven project and depends on Jedis to connect to Redis, Jackson to map to/from JSON and SLF4J for logging.
The project contains a client implementation as well as a worker implementation that supports listeners.
Jesque requires Java 17+. Download the latest source at:
https://github.com/gresrun/jesque
Or, to use it in your Maven project, add it as a dependency:
<dependency>
<groupId>net.greghaines</groupId>
<artifactId>jesque</artifactId>
<version>3.0.0</version>
</dependency>NOTE: If you are running Java 8 to Java 16, you can still use the 2.x versions, the latest of which is 2.3.0.
Starting with version 3.0.0, Jesque now requires Java 17 and had been modernized to use the latest Jedis client (7.2.x) as well as the latest version of Jackson (3.x).
The Jedis client changes will require minor refactoring if you were constructing pooled connections manually. Jackson 3.x has a comprehensive guide to the changes to that library, notably the exception types and ObjectMapper class. Jesque itself has some minor breaking API changes, includng how Config objects are constructed and types of durations and future times.
// Configuration
var config = Config.newBuilder()
.withHostAndPort(host, port)
.withPassword(password)
.build();
// Add a job to a queue
var job = new Job("TestAction",
new Object[]{ 1, 2.3, true, "test", List.of("inner", 4.5)});
var client = new ClientImpl(config);
client.enqueue("foo", job);
client.end();
// Start a worker to run jobs from that queue
var queues = List.of("foo");
var jobFactory =
new MapBasedJobFactory(Map.of("TestAction", TestAction.class))
var worker = new WorkerImpl(config, queues, jobFactory);
var workerThread = new Thread(worker);
workerThread.start();
// Enqueue more jobs, etc.
// Shutdown the worker when finished
worker.end(true);
try {
workerThread.join();
} catch (Exception e){
e.printStackTrace();
}If enqueueing multiple jobs at the same time, there is client.batchEnqueue(String queue, List<Job> jobs) which does it
in an optimized way.
Delayed jobs can be executed at sometime in the future:
var future = Instant.now().plusSeconds(10L);
client.delayedEnqueue("fooDelay", job, future);NOTE: Jesque's delayed jobs implementation is not compatible with resque-scheduler.
Recurring jobs can start at a specific time and execute at specified intervals:
var future = Instant.now().plusSeconds(10L);
var frequency = Duration.ofSeconds(60L);
client.recurringEnqueue("fooRecur", job, future, frequency));Delayed and recurring jobs can be cancelled:
client.removeDelayedEnqueue("fooDelay", job);
client.removeRecurringEnqueue("fooRecur", job);Using a pool of client connections is useful in multi threaded apps:
var jesqueClientPool =
new ClientPoolImpl(config, PoolUtils.createJedisPool(config));
jesqueClientPool.enqueue("foo", job);You can execute custom callbacks during specific Worker events:
int myVar = 123;
worker.getWorkerEventEmitter().addListener(
(event, worker, queue, job, runner, result, throwable) -> {
if (runner instanceof TestAction) {
runner.setSomeVariable(myVar);
}
}, WorkerEvent.JOB_EXECUTE);WORKER_STARTFinished starting up and is about to start running.WORKER_POLLPolling the queue.JOB_PROCESSProcessing a Job.JOB_EXECUTEAbout to execute a materialized Job.JOB_SUCCESSSuccessfully executed a materialized Job.JOB_FAILURECaught an Exception during the execution of a materialized Job.WORKER_ERRORCaught an Exception during normal operation.WORKER_STOPFinished running and is about to shutdown.
For more usage examples check the tests. The tests require that Redis is running on localhost:6379.
Use the resque-web application to see the status of your jobs and workers or, if you prefer Java, try Jesque-Web.
As mentioned Jesque depends on Jedis to connect to Redis.
You can configure Jesque to connect to Redis given a URL in a system property (as used in Heroku + RedisToGo) with the following snippet:
final Config.Builder configBuilder = Config.newBuilder();
try {
var redisUrl = new URI(System.getProperty("REDIS_PROVIDER", "127.0.0.1"));
configBuilder.withHostAndPort(redisUrl.getHost(), redisUrl.getPort());
String redisUserInfo = redisUrl.getUserInfo();
if (redisUserInfo != null) {
configBuilder.withPassword(redisUserInfo.split(":", 2)[1]);
}
} catch (URISyntaxException use) {
// Handle error
}
final Config config = configBuilder.build();- I chose to implement the jobs as classes that implement
java.lang.Runnableorjava.util.concurrent.Callable. If the job requires arguments (most do), there must be a constructor that matches the supplied arguments. I felt this was the most flexible option and didn't require the jobs to inherit or implement a special Jesque class. Because of this, the jobs don't even need to know about Jesque at all. Furthermore, the client need not have the job'sClassin its VM, it only needs to know the classname and all the parameters'Classes on its classpath. Only the workers realize the job and then run them. - I chose to use Jedis because:
- It is simple to use
- Fully supports Redis 2.0 and uses the new unified protocol
- No dependencies
- I chose to use Jackson because:
- I was already familiar with it
- It performs great and does what it says on the tin
- No dependencies
- I chose to use SLF4J because:
- It lets the application choose how to log
- No dependencies
If you are on Mac OS X, I highly recommend using the fantasic Homebrew package manager. It makes installing and maintaining libraries, tools and applications a cinch. E.g.:
brew install redis
brew install git
brew install mavenBoom! Ready to go!
Copyright 2026 Greg Haines
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.