Docs

Documentation versions (currently viewingVaadin 24)

Background Jobs

How to handle background jobs.

Many business applications need to perform in background threads. These tasks might be long-running operations triggered by the user, or scheduled jobs that run at specific times or intervals.

Working with more than one thread increases the risk of bugs. Furthermore, there are many different ways of implementing background jobs. To reduce the risk, you should learn one way, and then apply it consistently in all of your Vaadin applications.

Threads

Whenever you work with background threads in a Vaadin application, you should never create new Thread objects, directly. First, new threads are expensive to start. Second, the number of concurrent threads in a Java application is limited. While the exact limit depends on various factors, Java applications typically support thousands of threads.

Instead of creating threads manually, you should use either a thread pool, or virtual threads.

A thread pool consists of a queue, and a pool of running threads. The threads pick tasks from the queue and execute them. When the thread pool receives a new job, it adds it to the queue. The queue has an upper size limit. If the queue is full, the thread pool rejects the job, and throws an exception.

Virtual threads were added in Java 21. Whereas ordinary threads are managed by the operating system, virtual threads are managed by the Java virtual machine. They’re cheaper to start and run, which means you can have a much higher number of concurrent virtual threads than ordinary threads. If your virtual machine supports virtual threads, you should use them.

For more information on virtual threads, see the Java Documentation.

Task Execution

The background jobs themselves shouldn’t need to manage their own thread pools, or virtual threads. Instead, they should use executors. An executor is an object that takes a Runnable, and executes it at some point in the future. Spring provides a TaskExecutor, that you should use in your background jobs.

By default, Spring Boot sets up a ThreadPoolTaskExecutor in your application context. You can tweak the parameters of this executor through the spring.task.executor.* configuration properties.

To use virtual threads, you can enable them by setting the spring.threads.virtual.enabled configuration property to true. In this case, Spring Boot sets up a SimpleAsyncTaskExecutor, and creates a new virtual thread for every task.

You can interact with the TaskExecutor either directly, or declaratively through annotations.

When interacting with it directly, you would inject an instance of TaskExecutor into your code, and submit work to it.

Here is an example of a class that uses the TaskExecutor:

import org.springframework.core.task.TaskExecutor;

@Service
public class MyWorker {

    private final TaskExecutor taskExecutor;

    MyWorker(TaskExecutor taskExecutor) {
        this.taskExecutor = taskExecutor;
    }

    public void performTask() {
        taskExecutor.execute(() -> {
            System.out.println("Hello, I'm running inside thread " + Thread.currentThread());
        });
    }
}
Important
When you inject the TaskExecutor, you have to name the parameter taskExecutor. The application context may contain more than one bean that implements the TaskExecutor interface. If the parameter name doesn’t match the name of the bean, Spring doesn’t know which instance to inject.

If you want to use annotations, you have to enable them first. Do this by adding the @EnableAsync annotation to your main application class, or any other @Configuration class.

Here’s an example that adds the annotation to the main application class:

import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync
public class Application{

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

You can now use the @Async annotation to tell Spring to execute your code in a background thread.

Here is a version of the earlier MyWorker example, but using @Async instead of the TaskExecutor:

import org.springframework.scheduling.annotation.Async;

@Service
public class MyWorker {

    @Async
    public void performTask() {
        System.out.println("Hello, I'm running inside thread " + Thread.currentThread());
    }
}

For more information about task execution, see the Spring Documentation.

Task Execution Annotation Caveats

Using annotations makes the code more concise. However, they come with some caveats.

Should you forget to add @EnableAsync to your application, your @Async methods run synchronously in the calling thread instead of in a background thread. Also, you can’t call an @Async method from within the bean itself. This is because Spring by default uses proxies to process @Async annotations, and local method calls bypass the proxy.

In the following example, performTask() is executed in a background thread, and performAnotherTask() in the calling thread:

@Service
public class MyWorker {

    @Async
    public void performTask() {
        System.out.println("Hello, I'm running inside thread " + Thread.currentThread());
    }

    public void performAnotherTask() {
        performTask(); // This call runs in the calling thread
    }
}

If you interact directly with TaskExecutor, you’ll avoid this problem. In the following example, both performTask() and performAnotherTask() execute in a background thread:

@Service
public class MyWorker {

    private final TaskExecutor taskExecutor;

    MyWorker(TaskExecutor taskExecutor) {
        this.taskExecutor = taskExecutor;
    }

    public void performTask() {
        taskExecutor.execute(() -> {
            System.out.println("Hello, I'm running inside thread " + Thread.currentThread());
        });
    }

    public void performAnotherTask() {
        performTask(); // This method executes its task in a background thread
    }
}

Task Scheduling

Spring also has built in support for scheduling tasks through a TaskScheduler. You can interact with it either directly, or through annotations. With both, you have to enable it by adding the @EnableScheduling annotation to your main application class, or any other @Configuration class.

Below is an example that adds the annotation to the main application class:

import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
public class Application{

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

When interacting directly with the TaskScheduler, you’d inject it into your code, and schedule work with it.

This is an example that uses the TaskScheduler to execute the performTask() method every five minutes:

import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.scheduling.TaskScheduler;

@Service
class MyScheduler implements ApplicationListener<ApplicationReadyEvent> {

    private final TaskScheduler taskScheduler;

    MyScheduler(TaskScheduler taskScheduler) {
        this.taskScheduler = taskScheduler;
    }

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        taskScheduler.scheduleAtFixedRate(this::performTask, Duration.ofMinutes(5));
    }

    private void performTask() {
        System.out.println("Hello, I'm running inside thread " + Thread.currentThread());
    }
}

You can achieve the same using the @Scheduled annotation, like this:

import org.springframework.scheduling.annotation.Scheduled;

@Service
class MyScheduler {

    @Scheduled(fixedRate = 5, timeUnit = TimeUnit.MINUTES)
    public void performTask() {
        System.out.println("Hello, I'm running inside thread " + Thread.currentThread());
    }
}

For more information about task scheduling, see the Spring Documentation.

Task Scheduling Caveats

Spring uses a separate thread pool for task scheduling. The tasks themselves are also executed in this thread pool. That’s fine if you have a small number of short tasks. However, if you have many tasks, or long-running tasks, you may have problems. For instance, your scheduled jobs might stop running because the thread pool has become exhausted.

To avoid trouble, you should use the scheduling thread pool to schedule jobs. Then give them to the task execution thread pool to execute. You can combine the @Async and @Scheduled annotations, like this:

@Service
class MyScheduler {

    @Scheduled(fixedRate = 5, timeUnit = TimeUnit.MINUTES)
    @Async
    public void performTask() {
        System.out.println("Hello, I'm running inside thread " + Thread.currentThread());
    }
}

You can also interact directly with the TaskScheduler and TaskExecutor, like this:

@Service
class MyScheduler implements ApplicationListener<ApplicationReadyEvent> {

    private final TaskScheduler taskScheduler;
    private final TaskExecutor taskExecutor;

    MyScheduler(TaskScheduler taskScheduler, TaskExecutor taskExecutor) {
        this.taskScheduler = taskScheduler;
        this.taskExecutor = taskExecutor;
    }

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        taskScheduler.scheduleAtFixedRate(this::performTask, Duration.ofMinutes(5));
    }

    private void performTask() {
        taskExecutor.execute(() -> {
            System.out.println("Hello, I'm running inside thread " + Thread.currentThread());
        });
    }
}

Building

Implementing Jobs
How to implement background jobs.
Triggering Jobs
How to trigger background jobs.
UI Interaction
How to interact with jobs from the user interface.