Docs

Documentation versions (currently viewingVaadin 24)

Implementing Jobs

How to implement backgorund jobs.

When implementing a background job, it’s important to decouple its logic from how and where it is triggered. This ensures flexibility in triggering the job from different sources.

For instance, you may want to run the job every time the application starts. In this case, you may want to run it in the main thread, blocking the initialization of the rest of the application until the job is finished. You may also want to run the job in a background thread once a day — perhaps at midnight, or whenever a certain application event is published.

Here is a visual example of a job with three different triggers:

Job with Three Triggers

In code, a job is a Spring bean, annotated with the @Service annotation. It contains one or more methods, that when called, execute the job in the calling thread.

Below is an example of a Spring bean that implements a single background job:

import org.springframework.stereotype.Service;

@Service
public class MyBackgroundJob {

    public void performBackgroundJob() {
        ...
    }
}

For a job triggered from within the same package, the class can be package-private. A job triggered externally, must be public.

Transactions

A job that works on the database, should manage its own transactions. Because a job is a Spring bean, you can use either declarative or programmatic transaction management.

This is an example of a background job that uses declarative transaction management to ensure the job runs inside a new transaction:

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class MyBackgroundJob {

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void performBackgroundJob() {
        ...
    }
}

Security

Unlike application services, background jobs should not rely on method-level security. The reason is that Spring Security uses the SecurityContext to access information about the current user. This context is typically thread local, which means it’s not available in a background thread. Therefore, whenever the job is executed by a background thread, Spring would deny access.

When the background job needs information about the current user, this information should be passed to it by the trigger, as an immutable method parameter.

Batch Jobs

Consider implementing two versions of a batch job: one for processing all applicable inputs; and another for handling a specific set of inputs. This approach provides flexibility when you need to process individual cases or recover from errors.

For example, a batch job that generates invoices for shipped orders could look like this:

@Service
public class InvoiceCreationJob {

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void createInvoicesForOrders(Collection<OrderId> orders) {
        ...
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void createInvoicesForAllApplicableOrders() {
        ...
    }
}

In this example, the first method creates invoices for the orders whose IDs have been passed as parameters. The second generates invoices for all orders that have been shipped, but not yet invoiced.

Implementing batch jobs like this doesn’t require much effort if done from the start, but allows for flexibility that may be useful. Continuing on the invoice generation example, you may discover a bug in production. This bug causes some orders to have bad data in the database. As a result, the batch job won’t be able to generate invoices for them.

Fixing the bug is easy, but your users won’t want to wait for the next batch run to occur. Instead, you can add a button to the user interface that allows them to trigger invoice generation for an individual order.

Idempotent Jobs

Whenever you build a background job that updates or generates data, you should consider making the job idempotent. An idempotent job leaves the database in the same state regardless of how many times it’s been executed on the same input.

For example, a job that generates invoices for shipped orders should always check that no invoice already exists before it generates a new one. Otherwise, some customers may receive multiple invoices because of an error somewhere.

How to make a job idempotent depends on the job itself. This is outside the scope of this documentation page.