1. Introduction
I found out about JobRunr at a Java User Group and was intrigued by it's simplicity.
JobRunr turns background job processing in Java into what it should've been all along: stupidly simple. Just pass a lambda to schedule work, and JobRunr handles persistence, distribution, retries, and monitoring automatically.
Talk is cheap, here is the code to get you started immediately!
2. Why JobRunr
It's just Java. No interfaces to implement, no annotations required (though they're there if you want them).
Schedule a background job with a simple lambda:
BackgroundJob.enqueue(() -> emailService.send("user@example.com"));
That's it. JobRunr serializes the lambda to JSON, persists it to your database of choice (SQL or NoSQL), and handles execution across your cluster. Scale from one server to dozens without changing a single line of code.
2.1 Dashboard included
Out of the box, you get a React-based dashboard showing every job's status, execution history, and failures. No third-party monitoring tools needed.
2.2 Reliable
With automatic retries using exponential backoff, optimistic locking to prevent duplicate execution, and support for distributed processing, JobRunr handles the hard parts so you don't have to.
3. Best Practices
The following list is a subset of the complete best practices listed on the JobRunr website:
3.1 Keep jobs arguments simple
Method invocations (i.e. a job) are serialized during the creation process of the background job.
Arguments are converted into Json strings using the JobMapper class.
If you have complex entities and/or large objects; including arrays, it is better to place them into a database, and then pass only their identities to the background job.
So instead of this:
public void backgroundMethod(Entity entity) { }
Consider doing this:
public void backgroundMethod(long entityId) { }
3.2 Keep Job Lambda simple
JobRunr needs to analyze your job using ASM and reflection. The more instructions you put inside the Job lambda, the slower the enqueueing / scheduling will be.
So instead of this:
BackgroundJob.enqueue(reportService -> reportService.generateReport(
dtoConversionService.toDto(
new ReportRequest(Instant.now(), reportService.getPreviousReport())
)
);
Consider this:
ReportRequestDto reportRequestDto = dtoConversionService.toDto(new ReportRequest(Instant.now(), reportService.getPreviousReport()));
BackgroundJob.enqueue(() -> reportService.generateReport(reportRequestDto));
3.3 Do not catch Throwable
If your background job method catches java.lang.Exception or java.lang.Throwable and this exception is not rethrown, JobRunr is of course not aware that something went wrong and thinks the job finished successfully.
Instead of this:
public void sendWelcomeEmail(Long userId) {
try {
User user = userRepository.getUserById(userId);
emailService.Send(user.getEmailAddress(), "Hello!");
} catch (Throwable t) { // the exception is catched and JobRunr does not know that something went wrong!
t.printStackTrace();
}
}
Consider this:
public void sendWelcomeEmail(Long userId) throws Exception {
User user = userRepository.getUserById(userId);
emailService.Send(user.getEmailAddress(), "Hello!");
}
4. Gotchas
4.1 Database downtime kills everything
When your database goes down, the free version of JobRunr stops processing jobs completely and won't automatically resume when the database comes back up. This happens because JobRunr detects the connection loss and stops processing to prevent flooding your logs with failed attempts.
The workaround: JobRunr Pro monitors the StorageProvider and automatically restarts processing when it detects the database is back online. For the free version, you'll need to restart your application or implement your own monitoring.
4.2 Lambda Refactoring = NoSuchMethodException Hell
Here's a scenario that'll ruin your day: you have jobs scheduled for next week, then you refactor a method signature. If your servers run different code versions with incompatible method signatures, JobRunr throws NoSuchMethodException and job processing fails.
Why? JobRunr serializes the type name, method name, parameter types, and argument values to JSON. Change any of these, and queued jobs can't find their target method.
The fix: Keep lambda arguments simple and stable. Pass IDs instead of entire entities. If you must change a signature, drain your job queue first or use JobRunr Pro's upcasting features.
5. Conclusion
JobRunr delivers where Spring Batch feels like overkill and Quartz feels outdated. It's LGPL-licensed, integrates seamlessly with Spring Boot, Micronaut, and Quarkus, and handles the distributed processing complexity you don't want to think about.
Just remember: keep your lambdas simple, watch out for database failures in production, and test your recurring job schedules under load.