Best practices

These tips will help you keep background processing running smoothly and efficiently

Make job arguments small and 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 doing this:

public void backgroundMethod(Entity entity) { }

Consider doing this:

public void backgroundMethod(long entityId) { }

Make the Job Lambda as small as possible

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 doing this:

BackgroundJob.enqueue(reportService -> reportService.generateReport(
    dtoConversionService.toDto(
        new ReportRequest(Instant.now(), reportService.getPreviousReport())
    )
);

Consider doing this:

ReportRequestDto reportRequestDto =  dtoConversionService.toDto(new ReportRequest(Instant.now(), reportService.getPreviousReport()));
BackgroundJob.enqueue(() -> reportService.generateReport(reportRequestDto));

Making the arguments to your job as simple as possible will allow caching of job details. This means that enqueueing jobs (e.g. via web requests) will finish a lot faster.

Make your background methods re-entrant

Re-entrancy means that a method can be interrupted in the middle of its execution and then be safely called again. The interruption can be caused by many different things (i.e. exceptions, server shut-down), and JobRunr will attempt to retry processing many times until the job succeeded.

You may have many problems, if you don’t prepare your jobs to be reentrant. For example, if you are using an email sending background job and experience an error with your SMTP service, you can end with multiple emails sent to the addressee.

Instead of doing this:

public void sendWelcomeEmail(Long userId) {
	User user = userRepository.getUserById(userId);
    emailService.Send(user.getEmailAddress(), "Hello!");
}

Consider doing this:

public void sendWelcomeEmail(Long userId) {
	User user = userRepository.getUserById(userId);
    
	if (user.IsWelcomeEmailNotSent()) {
    	emailService.Send(user.getEmailAddress(), "Hello!");
        user.setWelcomeEmailSent(true);
        userRepository.save(user);
    }
}

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.

Never catch Exception or Throwable - JobRunr will take care of it for you!

So, never do 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();
    }
}

Instead, do this:

public void sendWelcomeEmail(Long userId) throws Exception {
    User user = userRepository.getUserById(userId);
    emailService.Send(user.getEmailAddress(), "Hello!");
}

If you’d like to log in case of an Exception or Throwable, you should do it as follows:

public void sendWelcomeEmail(Long userId) throws Exception {
    try {
        User user = userRepository.getUserById(userId);
        emailService.Send(user.getEmailAddress(), "Hello!");
    } catch (Exception e) { // the exception is caught and re-thrown allowing JobRunr to reschedule it.
        logger.error("Error running job", e);
        throw e;
    }
}